前言
本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!!
算法题(LeetCode刷题142环形链表II)—(保姆级别讲解)
分析题目:
- 其实本题目中主要解决两个问题,分别是:
算法思想
可以使用快慢指针法,分别定义 fast
和 slow
指针,从头结点出发,fast
指针每次移动两个节点
,slow
指针每次移动一个节点,如果 fast
和 slow
指针在途中相遇 ,说明这个链表有环。
为什么fast
走两个节点,slow
走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast
指针一定先进入环中,如果fast
指针和slow
指针相遇的话,一定是在环中相遇
,这是毋庸置疑的。
那么来看一下,为什么fast指针
和slow指针
一定会相遇呢?
可以画一个环,然后让 fast指针
在任意一个节点开始追赶slow指针
。
会发现最终都是这种情况, 如下图:
fast
和slow
各自再走一步, fast
和slow
就相遇了
这是因为fast
是走两步,slow
是走一步,其实相对于slow
来说,fast
是一个节点一个节点的靠近slow
的,所以fast
一定可以和slow
重合。
此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
假设从头结点到环形入口节点 的节点数为x
。 环形入口节点到 fast
指针与slow
指针相遇节点 节点数为y
。 从相遇节点 再到环形入口节点节点数为 z
。 如图所示:
那么相遇时: slow
指针走过的节点数为: x + y
, fast
指针走过的节点数:x + y + n (y + z)
,n
为fast
指针在环内走了n
圈才遇到slow
指针, (y+z)
为 一圈内节点的个数。
因为fast
指针是一步走两个节点,slow
指针一步走一个节点, 所以 fast
指针走过的节点数 = slow
指针走过的节点数 * 2
:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x
,因为x
表示 头结点到 环形入口节点的的距离。
所以要求x
,将x
单独放在左面:x = n (y + z) - y ,
再从n(y+z)
中提出一个 (y+z)
来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n
一定是大于等于1
的,因为 fast
指针至少要多走一圈才能相遇slow
指针。
这个公式说明什么呢?
先拿n
为1
的情况来举例,意味着fast
指针在环形里转了一圈之后,就遇到了 slow
指针了。
当 n为1
的时候,公式就化解为 x = z
,
这就意味着,从头结点
出发一个指针,从相遇节点
也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1
,在头结点处定一个指针index2
。
让index1
和index2
同时移动,每次移动一个节点
, 那么他们相遇的地方就是 环形入口的节点。
那么 n
如果大于1
是什么情况呢,就是fast
指针在环形转n
圈之后才遇到 slow
指针。
其实这种情况和n为1
的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1
指针在环里 多转了(n-1)
圈,然后再遇到index2
,相遇点依然是环形的入口节点。
环形链表II代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}
};
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
ListNode* fast = head;
ListNode* slow = head;
//设置两个指针,分别是快指针和慢指针,并且同时指向头节点。
while(fast != NULL && fast->next != NULL)
//这里判断快指针,为什么不判断慢指针呢?
因为快指针
一次移动为两步
,是慢指针
的两倍
,所以直接判断快指针即可。
同时因为快指针一次移动为两步,所以在移动之前需要判断快指针的后两个节点是否为空
。
slow = slow->next;
fast = fast->next->next;
//开始移动快指针和慢指针
if (slow == fast)
//代表已经证明有环,也证明快指针和慢指针现在已经相遇
ListNode* index1 = fast;
ListNode* index2 = head;
//既然快指针和慢指针现在已经相遇,我们就开始寻找环的入口点,即设置index1
指向相遇点处,index2
指向头节点处。
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
//当index1
和index2
开始相遇的时候代表此时指向的是环的入口处。
return NULL;
//如果没有环,则返回NULL
补充
在推理过程中,大家可能有一个疑问就是:为什么第一次在环中相遇,slow
的 步数 是 x+y
而不是 x + 若干环的长度 + y
呢?
首先slow
进环的时候,fast
一定是先进环来了。
如果slow
进环入口,fast
也在环入口,那么把这个环展开成直线,就是如下图的样子:
那又有一个问题,为什么fast
不能跳过去呢? 在刚刚已经说过一次了,fast
相对于slow
是一次移动一个节点,所以不可能
跳过去。
结束语
如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!