Given a linked list, return the node where the cycle begins. If there is no cycle, return null
.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?
思路:先用快慢指针判断是否存在换,如果存在,找出环的首尾slow,fast
从head开始遍历,依次与环中的所有元素比较,如果相等那就是环的头---链表中第一个进入环的元素
虽然通过了,但是复杂度高了,太慢了
/**
* 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) {
if(head==NULL)
return NULL;
ListNode *slow,*fast;
if( !hasCycle(head, &slow, &fast) ) // slow->……->fast形成一个圈,有可能slow等于fast
return NULL;
ListNode *pivot=head;//从头开始判断pivot是否在 slow->……->fast形成的圈里
while(true)
{
for(ListNode *tmp=slow;;tmp=tmp->next)
{
if(tmp==pivot)
return tmp;
if(tmp==fast)
break;
}
pivot=pivot->next;
}
}
private:
bool hasCycle(ListNode *head,ListNode **slow, ListNode **fast) {
if(head==NULL)
return false;
*slow=head;
*fast=head->next;
while( *fast != NULL && (*fast)->next!=NULL)
{
if( *slow==*fast || (*fast)->next == *slow)
return true;
*slow=(*slow)->next;
*fast=(*fast)->next->next;
}
return false;
}
};
-----------------------------------------------------------------------------------------------------
转自:http://blog.sina.com.cn/s/blog_6f611c300101fs1l.html
分析
后来找到了复杂度O(n)的方法,使用两个指针slow,fast。两个指针都从表头开始走,slow每次走一步,fast每次走两步,如果fast遇到null,则说明没有环,返回false;如果slow==fast,说明有环,并且此时fast超了slow一圈,返回true。
为什么有环的情况下二者一定会相遇呢?因为fast先进入环,在slow进入之后,如果把slow看作在前面,fast在后面每次循环都向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况---当快慢指针都在环内的时候不会出现快指针跑了好几圈才和慢指针相遇的情况。
扩展问题
在网上搜集了一下这个问题相关的一些问题,思路开阔了不少,总结如下:
1. 环的长度是多少?
2. 如何找到环中第一个节点(即Linked List Cycle II)?
3. 如何将有环的链表变成单链表(解除环)?
4. 如何判断两个单链表是否有交点?如何找到第一个相交的节点?
首先我们看下面这张图:
设:链表头是X,环的第一个节点是Y,slow和fast第一次的交点是Z。各段的长度分别是a,b,c,如图所示。环的长度是L。slow和fast的速度分别是qs,qf。
下面我们来挨个问题分析。
1. 方法一(网上都是这个答案):
第一次相遇后,让slow,fast继续走,记录到下次相遇时循环了几次。因为当fast第二次到达Z点时,fast走了一圈,slow走了半圈,而当fast第三次到达Z点时,fast走了两圈,slow走了一圈,正好还在Z点相遇。
方法二:
第一次相遇后,让fast停着不走了,slow继续走,记录到下次相遇时循环了几次。
方法三(最简单):两指针p1和p2,p1从头开始,p2从z点开始,步长为1,一直循环走,p1.p2相遇点则为入口Y点
第一次相遇时slow走过的距离:a+b,fast走过的距离:a+n*(b+c)+b。
a.当n=1的时候
因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(当n=1时)。
我们发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。
b.如果n不等于1,也这么做了: 2(a+b) = a+n(b+c)+b => a=n(b+c)-b
现让一指针p1从链表起点处开始遍历,指针p2从相遇点Z处开始遍历,且p1和p2移动步长均为1。则当p1移动a步即到达环的入口点,由a=n(b+c)-b可知,此时p2也已移动a步即n(b+c) - b步。由于p2是从Z处开始移动,故p2移动n(b+c)步是移回到了Z处,再退b步则是到了环的入口点Y。也即,当p1从头结点移动a步第一次到达环的入口点Y时,p2从z结点移动a步(可能走过了多圈),也恰好到达了该入口点。参考
- Node* findEntry(Node* head, Node* encounter)
- {
- Node *p1 = head, *p2 = encounter;
- while(p1 != p2)
- {
- p1 = p1->next;
- p2 = p2->next;
- }
- return p1;
- }
2. 我们已经得到了结论a=c,那么让两个指针分别从X和Z开始走,每次走一步,那么正好会在Y相遇!也就是环的第一个节点。
3. 在上一个问题的最后,将c段中Y点之前的那个节点与Y的链接切断即可。
4. 如何判断两个单链表是否有交点?先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的Z点是否在另一个链表上。
如何找到第一个相交的节点?求出两个链表的长度L1,L2(如果有环,则将Y点当做尾节点来算),假设L1比L2长x,那么L1从x位置起,L2从头开始算起的话 他们的长度就一样了(由于尾部是重叠一样长的),所以只要L1从x位置开始,L2从头开始,依次比较,第一个相等的元素就是所求
ps:在环中,快慢两指针,从同一位置z开始跑,每次都只能在z相遇。证明:假设在离z点c米的地方y点相遇 ,慢指针跑了c米,那快指针跑了2c 米,同时快指针跑了的长度等于 一个圈的长度L + c ,所以有2c=L+c,即L=c。(前面分析了不可能跳过慢指针跑n个圈)。也就是说慢指针跑了一圈 还是回到了z点,y点就是z点
Code:
class Solution {
public:
};
Code II:
class Solution {
public:
};
ps:自己提交的
/**
* 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) {
if(head==NULL)
return NULL;
ListNode *slow = head;
ListNode *fast = head;
do
{
if(slow==NULL || fast==NULL || fast->next == NULL)
return NULL;
slow=slow->next;
fast=fast->next->next;
}while(slow!=fast);
slow=head;
while(slow!=fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
};