题目描述
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Follow up: Can you solve this without using extra space?
思考
在《LeetCode(6):linked-list-cycle》中我们说到了如何使用快慢指针的方法判断一个链表是否有环,而本题的要求则是不仅要判断是否有环,还要我们找出环的起点。可以确定的是,本题与快慢指针仍然有所联系。那我们如何找到起点呢?我当时在想在使用快慢指针的时候,我们是只能知道两个指针p、q的相遇点在哪的,并且一旦这两个指针进入环中,那么对于p、q而言,起点已经和其他点看起来一致了,因为这毕竟是一个首尾相连的环。怎么办?我想到的方法是,再引入一个指针r,我们就叫它观察员吧,它从起点开始观察,每当q追上p且中途p与q都没有遇到r时,那么r就往前走一步,直到r能够观察到(也就是遇到)p或q中的一个,那么说明r就指向了环的起点。因为p、q一直在环里面呀,而且p是一步一步走的,那么只要r到达环的第一个节点,p就会遇到r,至于为什么要判断q等于r,是为了提高速度,因为q是每次走两步的,虽然在它追上p的过程中,它不一定能够遇到r,但是只要它遇到了r,我们就可以说r已经到达了环的起点。
具体代码如下:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == NULL || head->next == NULL){
return NULL;
}
ListNode* p = head->next;
ListNode* q = head->next;
ListNode* r = head;
while(p != r && q != r &&q != NULL && q->next != NULL){
p = p->next;
q = q->next->next;
if(p == q){
r = r->next;
}
}
if(q == NULL || q->next == NULL){
return NULL;
}
return r;
}
};
上面我们说到了我的一种思路,就是引入观察员,但显然我的这种算法效率不高,下面我们来看一下大神罗伯特·弗洛伊德的Floyd判圈算法,这也是LeetCode本题回答中的大家的常规思路。
如下图,假设我们的指针p、q均从X出发,p为慢指针,q为快指针,设两者为Z相遇,那么由它俩的速度关系,可以得到:
(a + b) * 2 = a + b + (b + c) * m
稍加变换:
a = (b + c) * (m - 1) + c
而b + c恰好是环的一圈,所以如果此时我们将p放置于X,q放置于Z,且两者的速度均变为1,则当p到达Y的时候,q也将到达Y,因而只要我们判断p、q相遇,即可得到环的起点Y。
感觉这种算法好厉害,一般情形下,效率比我上面的方法快了好多,该算法只需要让p、q相遇两次,而我的方法要相遇很多次,高下立判......
给出代码如下:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == NULL || head->next == NULL){
return NULL;
}
ListNode* p = head;
ListNode* q = head;
while(q != NULL && q->next != NULL){
p = p->next;
q = q->next->next;
if(p == q){
break;
}
}
if(p == q){
p = head;
while(p != q){
p = p->next;
q = q->next;
}
}
else{
return NULL;
}
return p;
}
};
不过对于本题给出的测试用例而言,我的方法甚至要更快一些,时间17ms,内存1152k,Floyd算法则用时26ms,内存1108k。当然,这应该是因为本题给出的例子前面的非环部分比较短或者圈比较小导致的。
本文介绍就到这里了,下回见~