在前些日子的博客中,我写了链表是否有环的一道题,而今天的这道题是上一道题的升级版,不仅要确定是否有环,还需要找到环的入口点。
题目如下所示:
比如用上面的链表举例,我们要确定链表中存在环已经不是难题了,但是怎样才能确定这个环是从哪里开始的呢?
下面我通过形象一点的图示来为大家解释:
图中是比较形象的一个环,假设从头开始到入环口的距离是L,从入环口到相遇点的距离是X,环的周长为C。
如果链表中存在环的话,那么快指针和慢指针在环内一定会相遇,这是我在前面的博客中证明过的。要找到环的入口点,首先要找到两个指针的相遇点。找到之后,我们来计算一下,快指针和慢指针分别走的距离是多少。
慢指针走了从头节点到相遇点的距离,一共是L+X。
快指针走的距离是分为三段的,首先慢指针走到L的中间时,快指针正好走到入环的地方;当慢指针走到入环口的时候,快指针可能走了很多圈(很多人认为快指针只走了一圈,这是不对的。如果圈很小的话,当慢指针走到入环口,快指针都走了很多圈了);最后快指针又从入环口走到了相遇点。因此快指针走的距离是L+N*C+X。
由于快指针走的长度是慢指针的二倍,可以得到
2*(L+X) = L+NC+X
化简可以得到L = NC -X。可以这样解释,当慢指针从头节点走到入环口的时候,快指针已经从入环口开始在里面转了很多圈然后走到了相遇点,因此我们有理由说从相遇点开始到入环口的距离等于从头结点走到入环口的距离。
这就是这道题的证明
代码如下:
struct ListNode *detectCycle(struct ListNode *head) {
//设置快慢指针
ListNode* slow = head;
ListNode* fast = head;
ListNode* meet = NULL;
//要想找到环的入口点,必须使两个指针先在环内相遇
//找相遇点
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
//当两个指针相遇的时候,记录下相遇点的位置,跳出循环
if(fast == slow)
{
meet = fast;
break;
}
}
//跳出循环的时候有两种情况
//1.如果fast或者fast->next为空,表示链表无环
if(fast ==NULL || fast->next == NULL)
return NULL;
//2.找到相遇点
//经过验证,meet到入口的长度一定等于从头到入口的长度
while(head != meet)
{
meet = meet->next;
head = head->next;
}
return meet;
}