这题真的难,想不到如何解题。最后看了题解发现可以画图求解。在此搬运一下题解
链接:https://www.nowcoder.com/questionTerminal/253d2c59ec3e4bc68da16833f79a38e4?answerType=1&f=discussion
来源:牛客网
这题我们可以采用双指针解法,一快一慢指针。快指针每次跑两个element,慢指针每次跑一个。如果存在一个圈,总有一天,快指针是能追上慢指针的。
如下图所示,我们先找到快慢指针相遇的点,p。我们再假设,环的入口在点q,从头节点到点q距离为A,q p两点间距离为B,p q两点间距离为C。
因为快指针是慢指针的两倍速,且他们在p点相遇,则我们可以得到等式 2(A+B) = A+B+C+B. (感谢评论区大佬们的改正,此处应为:如果环前面的链表很长,而环短,那么快指针进入环以后可能转了好几圈(假设为n圈)才和慢指针相遇。但无论如何,慢指针在进入环的第一圈的时候就会和快的相遇。等式应更正为 2(A+B)= A+ nB + (n-1)C)
由3的等式,我们可得,C = A。
这时,因为我们的slow指针已经在p,我们可以新建一个另外的指针,slow2,让他从头节点开始走,每次只走下一个,原slow指针继续保持原来的走法,和slow2同样,每次只走下一个。
我们期待着slow2和原slow指针的相遇,因为我们知道A=C,所以当他们相遇的点,一定是q了。
我们返回slow2或者slow任意一个节点即可,因为此刻他们指向的是同一个节点,即环的起始点,q。
using System.Collections.Generic;
/*
public class ListNode
{
public int val;
public ListNode next;
public ListNode (int x)
{
val = x;
}
}*/
class Solution
{
/*给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。*/
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead.next==null)//这个if是应付测试用例的
{
return null;
}
ListNode fast=pHead;
ListNode slow=pHead;
while(fast!=null && slow!=null)
{
ListNode temp=fast.next;
fast=temp.next;//快速指针,每次走两个单位
slow=slow.next;//慢指针,每次走一个单位
if(fast==slow)
{
fast=pHead;//若快指针和慢指针相遇,让快指针指回到开头,慢指针留在原地
while(fast!=slow)//每次开始走一个单位,两个指针再次相遇的时候就是环的入口
{
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
return null;
}
}
但是这种题解也没有讲的很清楚,如果出现快指针走了N圈才和慢指针相遇的情况如何证明这种方法也是有效的?
这里搬运另一位大神的解法证明:
链接:
https://cyc2018.github.io/CS-Notes/#/notes/23.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%8E%AF%E7%9A%84%E5%85%A5%E5%8F%A3%E7%BB%93%E7%82%B9
使用双指针,一个快指针 fast 每次移动两个节点,一个慢指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。
假设环入口节点为 y1,相遇所在节点为 z1。
假设快指针 fast 在圈内绕了 N 圈,则总路径长度为 x+Ny+(N-1)z。z 为 (N-1) 倍是因为快慢指针最后已经在 z1 节点相遇了,后面就不需要再走了。
而慢指针 slow 总路径长度为 x+y。
因为快指针是慢指针的两倍,因此 x+Ny+(N-1)z = 2(x+y)。
我们要找的是环入口节点 y1,也可以看成寻找长度 x 的值,因此我们先将上面的等值分解为和 x 有关:x=(N-2)y+(N-1)z。
上面的等值没有很强的规律,但是我们可以发现 y+z 就是圆环的总长度,因此我们将上面的等式再分解:x=(N-2)(y+z)+z。这个等式左边是从起点x1 到环入口节点 y1 的长度,而右边是在圆环中走过 (N-2) 圈,再从相遇点 z1 再走过长度为 z 的长度。此时我们可以发现如果让两个指针同时从起点 x1 和相遇点 z1 开始,每次只走过一个距离,那么最后他们会在环入口节点相遇。