Linked List Cycle
Problem I:
Given a linked list, determine if it has a cycle in it.
Problem II:
Given a linked list, return the node where the cycle begins, if there is no cycle, return null.
Follow up:
Can you solve it without using extra space?
分析:
第一个问题要求检测链表中是否有环路。解决思路很直观,用快慢指针,一个一次一步,另一个一次两步,如果有环路,两个指针必然相遇。
第二问是在第一问的基础上进一步讨论环路的起点在哪里,间接的问题也是在问两个指针相遇在哪里。先看例子,比较直观。下图中的链表一共有5个节点组成,其中3个构成环路。慢指针从头节点开始,需要两步走到环路的起点(3),快指针一次走两步,两次到(5)。两个指针的相遇是在(4)上,慢指针一共走了3步,快指针一共走了6步。接下来做点简单的理论分析。有以下几个结论:
(1) 相遇的地点一定在环路上;
(2) 相遇时,慢指针一定在环路上走了小于等于一圈;
(3) 假设慢指针需要k步走到环路的起点,相遇时慢指针走了m步,那么快指针走了2m步,且相遇点是在偏离环路起点m-k的位置(0≤m-k<n,其中n为环路的节点数),并且m = λn。
结论(1)是显然的。两个同时出发的指针,一个速度是另外一个的两倍,如果一直走在直线上,永远都不可能相遇。有环路时,一旦进入环路就不可能出来,所以相遇的地点必然在环路上。
结论(2)也比较显然,两个指针如果绕着一个环开始走,慢指针走了一圈时,快指针一定走了两圈,两者相遇的地点恰好是起始的位置。而如果环路的前面有一段直线,当慢指针走到环路的起点时,快指针已经绕着环走了一些时候,快指针所处的位置,如果在环路起点,则与前面讨论的情况一样,它们会在环路起点相遇;如果在环路上别的位置,就已经领先了慢指针一些距离,那么慢指针一定走不满一圈就被追上了。
有了结论(2),相遇时,快指针走2m步也就是显然的。
假设相遇点偏离了环路起点x步,对于慢指针在环路外走了k步,环路上走了x步,一共走了m步,所以
k + x = m
对于快指针,在环路外走了k步,环路上绕着环至少绕了一圈,设走了λn步(λ≥1),一共走了2m步,所以
k + (x + λn) = 2m
两式相减得到m = λn。所以相遇点x = m - k,也即x = λn - k,这个关系提示我们,如果从相遇点开始走,那么k步之后可以走回起始点,所以可以让一个指针从其实位置走,另外一个指针从相遇点开始走,k步之后它们会在环路的起点相遇。
对于图中的例子,k = 2,x = 1,m = 3,n = 3,λ = 1。k越大,环越小λ也会越大,相遇也就越慢,不过不管k有多大,快指针也不会访问整个链表两遍,所以不用担心算法很慢,还是线性的时间复杂度。题目的代码比分析要简洁的多了。
1 class Solution { 2 public: 3 ListNode *detectCycle(ListNode *head) { 4 if(head == NULL) 5 return NULL; 6 7 ListNode *slow, *fast; 8 slow = fast = head; 9 while(fast->next != NULL && fast->next->next != NULL) { 10 slow = slow->next; 11 fast = fast->next->next; 12 if(slow == fast) { // meet 13 slow = head; 14 while(slow != fast) { 15 slow = slow->next; 16 fast = fast->next; 17 } 18 return slow; 19 } 20 } 21 22 return NULL; 23 } 24 };