求链表的环节点
这道题的思路应该是这样的:
- 先判断有没有环。
1.判断有没有环
我们通过 快慢指针 的方法,可以确认是否有环的存在,直接上图:
先初始化,让fast和slow都指向头结点(起点),slow每次走一步,fast每次走两步。
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
return true;
}
}
return false;
while 这么写的依据是,如果无环,则slow在走完前不可能和fast相遇,并且在fast走完后,循环就停止了,返回false
如果有环,当fast在环里饶了n环后,终于等到了slow。再回顾一下,通过上述,我们知道证明有环的条件就是fast与slow相遇。
那我们又是怎么知道这样走fast一定会和slow相遇,而不会谁多走一步或者少走一步刚好错过呢,会不会永远不会相遇?
这就又要另外细说了😸
###为什么fast和slow一定会相遇
当slow进入环的那一刻,fast与slow之间的最大距离就已经确立,fast开始追slow,设此时fast与slow相距路程N
fast每走两步,slow走一步,则相当于slow不动,fast走一步,路程=N-1
fast 2步 slow 1步 …
N-1
N-2
N-3
.
.
.
0
到最后,fast一定会与slow相遇。以上证明了 fast走两步,slow走一步,如果存在环,它们最后一定会相遇。
代码
struct ListNode
{
int val;
struct ListNode *next;
};
bool hasCycle(struct ListNode *head) {
struct ListNode*slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
return true;
}
}
return false;
}
拓展: 那如果fast走3步、走4步、…走n步呢?
假设刚入环时,slow和fast相距路程N为偶数。
fast每走三步==(奇数步)==,slow走一步,则相距路程N的变化为
N-2
N-4
N-6
.
.
.
0
N的变化图
我们设 fast在后面追slow的方向为正,则-1的含义是fast在slow前距离为1的地方。
fast和slow的距离为N此时变成了C-1。如果fast想追上slow 他需要再跑一趟,
注意:
有些小伙伴会有疑问,fast是已经经过了slow,和slow相遇后,然后才到slow前面的,难道这不算相遇吗?
注意:这道题的本质还是链表,判断相遇点的代码是这样的
while(fast!=slow)
{
slow=slow->next;
fast=fast->next->next;
}
所以 fast与slow相遇后才走到slow前的说法 其实是不正确的。
从代码中我们可以看出,fast最终被赋值为 fast的next的next。 也就是说fast是跳到了slow的前面,并没有在slow处的停留,也就不存在相遇。
举个例子:你对别人讲你开车经过了北京,你能说你到过北京吗。不能吧,这是有区别的。
回到正题:
回顾一下 ,上面我们假设过 :slow进环时,fast、slow相距路程为N,环的长度为C。
N变成了C-1,即N=C-1;
上述说过,fast走奇数步,且N为奇数时,最后路程一直减减减,会减到-1,(忘了的小伙伴点目录里的N的变化图再上去看一眼)
而减到-1就意味着错过了,要再重新追slow。
因此,当C为偶数时,即C-1为奇数,经过第一轮追赶后,N=C-1为奇数,则第二轮追赶又错过了,第三轮追赶,又变成了N=C-1。…
因此,针对 如果fast走3步、走4步、…走n步是否会相遇的问题,我们有如下:
结论
无论fast走多少步,这都不重要,重要的是如果slow进环时,fast与slow相距路程N为奇数,且环的长度C为偶数(C-1为奇数),那么fast与slow将会在环里一直打圈,永远无法相遇
2.环节点的位置分析
前方高能!!!
还是这个图,现在我们开始推结论。
slow、fast相遇时(注意:这里是slow每走一步,fast走两步)
slow所走路程为 | fast所走路程为 |
---|---|
L+x | L+n*c+x |
解释一下n*c,因为环的长度未知,如果环很大,可能slow入环前fast还没走完一圈,也可能环很小,slow进环前fast已经走了很多圈。
已知fast的步数是slow的两倍,则fast所走路程为slow的两倍,有:
2(L+x)=L+n*c+x,则L=n*c-x
直线的尽头是环口。
既然L=n*c-x,那我们就把x减掉,从减掉x后的起点出发,即相遇点,那这段减掉了x的路程,就和L一样长。
意思是,只要fast从 相遇点 出发(转几圈其实不重要),slow从 起点 出发,当slow走到入环口的时候,fast也走到入环口了,他们相遇的位置,就是入环口,
而在第一部分的时候,已经提到了如何判断是否存在环——>slow、fast最后会相遇。
那时候就已经可以得到了他们的相遇点。
因此,求环的入口,只要将fast和slow分别赋值为:
fast=meet(相遇点)
slow=head(起点)
然后一直循环直到他们相遇,记录这个点,即是入环节点。
代码
struct ListNode* detectCycle(struct ListNode* head)
{
struct ListNode* slow, * fast;
slow = fast = head;
struct ListNode* meet;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
struct ListNode* meet = fast;
slow = head;
while (meet != slow)
{
slow = slow->next;
meet = meet->next; //因为路程一样长,所以meet和slowd都是一次走一步。
}
return meet;
}
}
return NULL;
}