进阶:你是否可以不用额外空间解决此题?
方法一:哈希表
时间复杂度:O(n) 空间复杂度:O(n)
/**
* 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) {
set<ListNode*> s;
int n=0;
ListNode*p=head;
while(p){
if(s.find(p)!=s.end())
return p;
s.insert(p);
p=p->next;
}
return NULL;
}
};
方法二:双指针
想法
当然一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。
算法
Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。
阶段 1
这里我们初始化两个指针 - 快指针和慢指针。我们每次移动慢指针一步、快指针两步,直到快指针无法继续往前移动。如果在某次移动后,快慢指针指向了同一个节点,我们就返回它。否则,我们继续,直到 while 循环终止且没有返回任何节点,这种情况说明没有成环,我们返回 null 。
环中的节点从 0 到 C-1编号,其中 C 是环的长度。非环节点从−F到−1编号,其中F 是环以外节点的数目。F次迭代以后,慢指针指向了 0 且快指针指向某个节点 h,其中 F≡h(modC) 。这是因为快指针在 F 次迭代中遍历了2F 个节点,且恰好有 F 个在环中。继续迭代 C-h次,慢指针显然指向第 C-h号节点,而快指针也会指向相同的节点。原因在于,快指针从h号节点出发遍历了 2(C-h)个节点。
h+2(C−h)
=2C−h
≡C−h(modC)
因此,如果列表是有环的,快指针和慢指针最后会同时指向同一个节点,因此被称为相遇 。
阶段 2
给定阶段 1 找到的相遇点,阶段 2 将找到环的入口。首先我们初始化额外的两个指针: ptr1 ,指向链表的头, ptr2 指向相遇点。然后,我们每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口,返回这个节点。
我们利用已知的条件:慢指针移动 1 步,快指针移动 2 步,来说明它们相遇在环的入口处。(下面证明中的 tortoise 表示慢指针,hare 表示快指针)
2⋅distance(tortoise)
2(F+a)
2F+2a
F
=distance(hare)
=F+a+b+a
=F+2a+b
=b
因为 F=b,指针从 h 点出发和从链表的头出发,最后会遍历相同数目的节点后在环的入口处相遇。
时间复杂度:O(n)
对有环列表,快指针和慢指针在 F+C-h次迭代以后会指向同一个节点,正如上面正确性证明所示, F+C−h≤F+C=n ,所以阶段 1 运行时间在O(n) 时间以内,阶段 2 运行F<n 次迭代,所以它运行时间也在O(n) 以内。
对于无环链表,快指针大约需要迭代 2n次会抵达链表的尾部,这样不会进入阶段 2 就直接退出。因此,不管是哪一类链表,都会在与节点数成线性关系的时间内运行完。
空间复杂度:O(1)
Floyd 的快慢指针算法仅需要几个指针,所以只需常数级别的额外空间。
/**
* 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* slow=head;
ListNode* fast=head;
ListNode* p1=head,*p2=NULL;
while(fast!=NULL && fast->next!=NULL){ //判断是否有环
slow=slow->next;
fast=fast->next->next;
if(slow==fast){ //如果有环,将相遇点记为p2
p2=slow;
break;
}
}
if(p2){ //当有环时,从头指针p1和相遇点p2同时向前移动,会在出口相遇
while(p1!=p2){
p1=p1->next;
p2=p2->next;
}
return p1;
}
return NULL; //没有环返回NULL
}
};