一、链表

链表是由一组在内存中不必相连(不必相连:可以连续也可以不连续)的内存结构Node,按特定的顺序链接在一起的抽象数据类型。

我们常见的链表结构有单链表和双向链表。

单链表,保存了下一个结点的指针,可以根据下一结点指针向下遍历

双向链表,保存了向上一个结点和向下一个结点的指针,所以可以向上下两个方向遍历。

单链表结构一般如下:

template<typename T>
struct Node
{T data; //数据Node* next; //指向下一个结点的指针
};

双向链表的结构一般如下:

template<typename T>
struct Node
{T data; //数据Node* previous; //指向上一个结点的指针Node* next; //指向下一个结点的指针
};

在算法中,我们遇到的链表题目一般可以使用一些经典的思路求解。本篇博客主要讨论这些经典的链表解题思路之一,快慢指针。

二、快慢指针

快慢指针也称龟兔赛跑算法,又叫判圈算法。具体方法是声明两个指针fast 指针和slow 指针,这两个指针按照题目的需求有不同的行进方案(例如fast每次行进2步,slow每次行进1步),但基本上步长是不同的,所以叫龟兔赛跑算法。

比较经典的题目如下,判断链表是否有环,所以又叫判圈算法。

【题目】环形链表

判断一个单链表是否有环,并且返回第一个入环节点。

一个单链表有环,即如下形式,图中第一个入环节点为2

<算法简述>

使用快慢指针求解这个问题。我们使fast和slow同时从头结点出发,同频行进。

其中fast每次走2个结点,slow每次走一个结点。

如果fast或 slow能够为nullptr,则链表无环,如果fast和slow相遇,则链表有环。

这里很容易比较得出快慢指针的一个特点1:可以判圈

使slow指针在原地(相遇位置)保持不动,并使fast指针返回头节点处,二者同频出发,每次均走一步。则再次相遇的节点为第一个入环节点。

<C++代码实现>

ListNode* hasCycle(ListNode* head)
{ListNode* fast = head;ListNode* slow = head;//判断是否有环while (nullptr != fast && nullptr != slow){if (nullptr == fast->next) return nullptr;fast = fast->next->next;slow = slow->next;//无环,返回空指针if (nullptr == fast || nullptr == slow){return nullptr;}if (fast == slow){break;}}//快指针回到头结点,快慢指针每次均走一步,相遇节点为第一个入环节点fast = head;while (nullptr != slow){if (fast == slow){break;}fast = fast->next;slow = slow->next;}return slow;
}

<复杂度分析>

时间复杂度为O(N),额外空间复杂度为O(1)。

【题目】判断一个单链表是否是回文结构

回文结构:正向遍历和反向遍历每个结点都是相同数据的结构。例如1->2->1,1->2->2->1都是回文结构。

【方法一】使用栈结构(对照组)

想到判断回文结构 ,我们可以想到栈结构,因为栈是符合先入后出的数据结构,所以只要我们先遍历一遍链表,对数据进行压栈。再依次弹出栈顶,就会得到链表的反向输出。这样可以同时取到原链表的正向输出和反向输出,一一比较之后,就可以判断是否回文。

<C++代码实现>

bool isPalindrome(Node* hd)
{stack<int> st;Node* temp = hd;while (nullptr != temp){st.push(temp->data);temp = temp->next;}temp = hd;while (nullptr != temp){if (temp ->data != st.top()){return false;}temp = temp->next;st.pop();}return true;
}

<复杂度分析>

上述额外使用栈结构,所以额外空间复杂度为O(N),时间复杂度为O(N)。

【方法二】快慢指针方法

分析:这个题目一般想不到使用快慢指针方法,但是快慢指针具有一个和此题吻合的特点2:利用快慢指针的不同步数,可以找到链表的中点。如果此题找到中点,我们也可以镜像地比较中点左右的元素,得出是否是回文结构的结论。

而使用下列方法,可以使额外空间复杂度为O(1),这就是这个算法的优势。

bool isPalindrome(Node* hd)
{if (nullptr == hd){return true;}Node* fast = hd->next;Node* slow = hd;//快慢指针同时遍历,快指针每次走2步,慢指针每次走1步//当快指针到终点的时候,慢指针正好处于中点位置while (nullptr != fast && nullptr != fast->next){fast = fast->next->next;slow = slow->next;}//慢指针继续往下遍历,遍历的同时将指向反向Node *temp1 = slow; //temp1用于记录slow的前一个结点,用于逆序链表的后半部分Node *temp2 = slow->next; //temp1用于记录slow的后一个结点,放在链表断开后指针丢失Node *mid = slow;   //保存中点结点,便于后续使用while (nullptr != slow){slow->next = temp1;temp1 = slow;slow = temp2;if (nullptr != slow){temp2 = slow->next;}}//此时temp1为原链表终点结点,使temp2指向头结点,同时遍历temp1,temp2比较数值Node *ed = temp1; //保存终点结点,便于后续还原链表temp2 = hd;bool ret = true; //是否是回文结构while (temp1 != mid){if (temp1->data != temp2->data){ret = false;break;}temp1 = temp1->next;temp2 = temp2->next;}//从终点开始,还原原链表temp1 = ed->next;ed->next = nullptr;while (mid != ed){temp2 = ed;ed = temp1;temp1 = ed->next;ed->next = temp2;}return ret;
}

<复杂度分析>

可见此算法的coding复杂很多,但是额外空间复杂度可以做到O(1),时间复杂度为O(N)。

【题目】删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

<分析>

此题的重点难点是:如何找到倒数第N个结点。

做完上面的判断回文结构的题,我们认识到快慢指针的特点2,应该可以很容易想到:我们使fast先走N个结点,再让slow和fast同频同步行进,当fast为最后一个结点的时候,slow所在结点即为倒数第N个结点。

<C++代码实现>

ListNode* removeNthFromEnd(ListNode* head, int n)
{ListNode* fast = head;ListNode* slow = head;//fast指针先走n步while (nullptr != fast && --n > 0){fast = fast->next;}//如果链表长度小于n,则直接返回头指针if (nullptr == fast){return head;}//如果倒数第n个就是头节点,则换头节点if (nullptr == fast->next){head = head->next;delete slow;return head;}//fast和slow都前进,fast走到倒数第一个,则删除slow->next节点while (nullptr != fast->next->next){slow = slow->next;fast = fast->next;}fast = slow->next;slow->next = slow->next->next;delete fast;return head;
}

三、总结

从上面的题目中,总结下快慢指针的一般特点

1、可以判断链表是否有环,并得出入环节点;

2、可以定位链表中的某个指定位置的结点;

后续在求解问题的过程中,如果还发现其他妙用,博主会持续更新。

链表-快慢指针(C++)相关推荐

  1. LeetCode 2130. 链表最大孪生和(链表快慢指针+反转链表+双指针)

    文章目录 1. 题目 2. 解题 1. 题目 在一个大小为 n 且 n 为 偶数 的链表中,对于 0 <= i <= (n / 2) - 1 的 i ,第 i 个节点(下标从 0 开始)的 ...

  2. java单链表快慢指针

    例题: LeetCode876. 链表的中间结点 /*** Definition for singly-linked list.* public class ListNode {* int val;* ...

  3. 环形链表[快慢指针 入环点]

    环&快慢指针 前言 一.环形链表 二.快慢指针 1.相遇即有环 2.入环点 总结 参考文献 前言 对于环问题,快慢指针是一种常用的手段.一步两步走,通过判定是否相遇来判定是否存在环.而且快慢指 ...

  4. 一文学会链表快慢指针解题技巧

    前言 上文 我们详细地学习了链表的基本概念,优缺点,也带大家一步步由浅入深地学习了链表的翻转技巧,这一篇我们来看看链表的另一个解题技巧:快慢指针. 快慢指针在面试中出现的概率也很大,也是务必要掌握的一 ...

  5. 动画:链表快慢指针解题技巧

    点击蓝色"五分钟学算法"关注我哟 加个"星标",天天中午 12:15,一起学算法 作者 | 码海 前言 这一篇我们来看看链表的另一个解题技巧:快慢指针. 快慢指 ...

  6. 46 - 算法 -Leetcode-141-环形链表-快慢指针

    * 快慢指针法:设定两个指针,如果快指针被慢指针追上,那么一定有环*/ class Solution {public:bool hasCycle(ListNode *head) {ListNode * ...

  7. 1-算法leetcode 876 快慢指针

    链表 – 快慢指针解决问题 链表多用指针 LIstNode * 解决问题 一个或者多个指针 初始条件需要考虑 ListNode *fast = head; ListNode *low = low; w ...

  8. python数据结构,快慢指针

    最近听的b站左程云数据结构,记录下来题和代码,python实现 链表 快慢指针 题目1:返回链表的第一个环结点,没有环就返回空 题目2:判断两个链表有没有共同结点,返回第一个共同结点,在没有环的情况下 ...

  9. 算法基础--快慢指针

    快慢指针 快慢指针就是用一快一慢两个指针来遍历数据对象并解决问题,常用于解决例如2倍速来求中间指针或判断循环链表.恒定n个差值或用于寻找倒数第n个指针等问题. 1.环形链表 快慢指针的经典应用场景就是 ...

  10. 尝试证明快慢指针可以相遇问题 以及 证明入环点问题

    预热 引用题目:环形链表 快慢指针解法: class Solution {public boolean hasCycle(ListNode head) {if(head == null) {retur ...

最新文章

  1. go语言中无法获取goroutine相关的信息
  2. 插值算法C实现(一元全区间)
  3. 编写高质量代码:改善Java程序的151个建议(第4章:字符串___建议52~55)
  4. 2021-11-06深度学习
  5. mysql没多久自动断开服务_mysql 长时间没连接了 就会自动断开服务
  6. 身为“月光族”的电视选择之道
  7. 学习笔记 ---- 设计模式之观察者模式
  8. 清北2017夏令营考试 day2
  9. div在html中的好处,详解DIV+CSS布局的好处和意义
  10. 软件测试工程师简历项目经验怎么写?一千个软件测试简历范文模板
  11. Excel单元格科学计数法转换和小数点取整
  12. Android多开检测的另一个思路
  13. 浙江大学三位一体招生?
  14. js的this指向总结
  15. [VBA学习]关于Formula以及数字格式的问题
  16. Ubuntu16.04下xbrowser远程图形化桌面配置(简单几步完成~)
  17. Vue | 指令实现自动填充英文名功能
  18. (专升本)PowerPnt(PowerPoint 2010的其他功能)
  19. web安全入门(第七章-1)文件上传漏洞--解析、验证、伪造
  20. 4种SpringBoot 接口幂等性的实现方案!最后一个80%以上的开发会踩坑

热门文章

  1. 对项目工时的估算: PERT(计划评审技术) 三点估算法
  2. 怎样修改MySQL数据库的密码
  3. myeclipse复制的文件代码乱码
  4. 计算机专业论文答辩ppt,计算机行业毕业论文答辩PPT.pptx
  5. 网易云音乐下载软件(上)
  6. 自适应滤波器 | 频域自适应滤波器(FLMS)
  7. 1206、0805、0603、0402、0201、01005、008004,你手工焊接过的最小的封装是哪个
  8. 正运动技术 运动控制卡应用开发教程之C#
  9. python中pandas库的作用_Python数据分析之Pandas库(笔记)
  10. ffmpeg合并mkv视频文件和ass字幕文件