题目
思路分析
这道题太经典了。不知道近年的行情怎么样,反正四年前秋招找工作的时候,这道题被问过无数遍。幸亏当时只考察代码怎么写,从来没有面试官问过我:为什么用快慢指针在环里就一定能相遇呢?为什么快指针的速度一定是2呢?3行不行?现在想想,我只适合做一个程序员而不是架构师,就是因为我好像只能根据描述快速实现代码,很少去思考答案背后的东西,这个习惯真是太不好了。
我们尝试回答上面的几个问题。首先,为什么用快慢指针一定能相遇?这个问题比较简单,每次前进快指针都比慢指针多走一个节点,假设环的路径大小为len,那么一定存在一个时刻x,使1节点*x=len,此时快指针追上慢指针。
第二个问题是,快慢指针的速度一定要是2和1吗?我个人觉得,如果题目的要求只是“求链表中是否存在环”,那么只要快指针比慢指针快,无论速度多少都可以。假设快指针速度为a,慢指针速度为b,正好相遇的时刻是x,那么只要满足(a-b)x=n * len就可以,显然这个式子总能找到解。但是如果像这道题一样,要求求出环的开头节点,那么速度用其他值应该比较难求出来。这点看完下面的证明相信应该能感受到。
最后的问题是,这道题如何求解?即使过去了4年,看到这道题我还能想起来解法是:快指针每次移动2个节点,慢指针每次移动1个节点,快慢指针相遇时,新增一个指针从链表开头出发,与慢指针同时移动,每次移动1个节点,当它与慢指针相遇时,所在位置就是环的入口。但是,为什么呢?
我们假设两指针相遇在点A,相遇时假设快指针经过了k圈,慢指针经过了k’圈,那么快指针经过的路程为a+k(b+c)+b,慢指针经过的路程为a+k’(b+c)+b,又因为快指针的路程=慢指针的路程*2,所以有:
a+k(b+c)+b=2(a+k’(b+c)+b)
a=(k-2k’)(b+c)-b
a=(k-2k’)(b+c)-(b+c)+c
a=(k-2k’-1)(b+c)+c
假设这时候一个新指针从起点出发,慢指针从相遇点出发会发生什么呢?新指针从起点走到入环点,经过的路程距离是a,那么根据上面的式子,慢指针此时经过的距离一定是(k-2k’-1)(b+c)+c,也就是说,慢指针经过了k-2k’-1圈环的距离+相遇点再走到入环点的距离c!此时慢指针一定也停在了入环点!多么神奇!
那么我们再回到那个问题:如果快指针的速度是慢指针的任意倍为什么不可以?假设是这样,那么上面计算出来的公式里b+c前面的系数不一定是整数,因此不可以。有兴趣的朋友可以自行证明。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
if (slow && slow->next) {
slow = slow->next;
} else {
return NULL;
}
if (fast->next->next) {
fast = fast->next->next;
} else {
return NULL;
}
while (fast && (fast != slow)) {
slow = slow->next;
if (fast->next) {
fast = fast->next->next;
} else {
return NULL;
}
}
if (fast != slow) {
return NULL;
}
ListNode *res = head;
while (res != slow) {
res = res->next;
slow = slow->next;
}
return res;
}
};