题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
假设链表存在回环,如上图所示。设A为链表的起点,B为回环的起点。现在我们设定两个指针Fast(每次运动2个结点)和Slow(每次运动一个结点)。
首先我们先证明他们一定存在运动到同一点C的必然性。设AB的距离为m,BC的距离为n,环的长度为r。我们知道Fast走的长度为m+n+k1*r, Slow走的长度为m+n+k2*r,其中k1和k2为整数,又因为相同时间Fast走的距离是Slow的两倍,所以2*(m+n+k2*r)=m+n+k1*r。也就是说,m+n=(k1-2k2)*r。因为环的形状是给定的,所以m和r的值是固定的,我们必然可以找到一个n来满足上面的式子(k1和k2可以随意指定)。并且我们可以看出AC的长度m+n是环长度的整数倍。
证明了一定可以找到相遇的交点c以后,我们让Slow回到起点,保持运动速度不变。Fast留在C点,运动速度改为和Slow一样,每次运动1个结点。当Slow从A点到B的时候运动了m的长度,而此时Fast从C点向前运动(逆时针)继续运动了m,相当于从B点开始运动了m+n,正好是环的整数倍,也就是Slow和Fast相遇在了B点,正好是我们想要找的环的起点。
下面是这种方法对应的C++算法:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead == NULL){
return NULL;
}
ListNode* meetingnode = MeetingNode(pHead);
if(meetingnode == NULL){
return NULL;
}
ListNode* pNode1 = meetingnode;
// 两个指针同时移动,找到环入口
ListNode* pNode2 = pHead;
while(pNode1 != pNode2){
pNode1 = pNode1->next;
pNode2 = pNode2->next;
}
return pNode1;
}
private:
// 使用快慢指针,找到任意的一个环中结点
ListNode* MeetingNode(ListNode* pHead){
ListNode* pSlow = pHead->next;
if(pSlow == NULL){
return NULL;
}
ListNode* pFast = pSlow->next;
while(pFast != NULL && pSlow != NULL){
if(pFast == pSlow){
return pFast;
}
pSlow = pSlow->next;
pFast = pFast->next;
if(pFast != NULL){
pFast = pFast->next;
}
}
return NULL;
}
};