一、单链表是否有环
题目描述
快慢指针:若链表有环,则两指针必在将来某一时刻相遇:
- 直观来看:本质上就是物理上的相对运动。快指针每次2步,慢指针每次1步。
如果没有环,快指针先到达链尾,结束;
如果有环,相对速度为1,即相当于慢指针静止,快指针每次1步,则必然在一圈之内相遇。 - 那如果快指针每次3步,4步呢?由之前的相对运动,我们知道两个指针不一定相遇。那么什么情况下可以相遇呢?
当S第一次到达环口,F可能已经在环里转了n圈。假设S的速度为\(v_s\),F的速度为\(v_f\),环长为\(L\),经过时间\(t\)相遇: \[disS=L_1, disF=L_1+L_2+nL\]
即问题转化为是否存在正整数\(t\),使得S和F在环内走过的路程相等: \[v_st\%L=(L_2+nL+v_ft)\%L\] 根据模运算性质: \[(L_2+nL+(v_f-v_s)t)\%L=0\] 再化简: \[(L_2+(v_f-v_s)t)\%L=0\] 也就是当\(L_2+(v_f-v_s)t\)是环长\(L\)的整数倍,快慢指针可以相遇。
回头去看最简单的情形:\(v_f-v_s=1\),则\(t=mL-L_2\),取\(m=1,t=L-L_2\)。所以经过\(t\)步必然相遇。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 单链表定义
struct ListNode{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL) {}
};
class Solution {
public:
bool hasCycle(ListNode* head)
{
if (head == NULL)
return false;
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
};
二、寻找环的入口
题目描述
设链头距离环的入口距离为\(L_1\),相遇点距离入口距离为\(L_3\),环的长度为\(L\):
证明的本质在于求出\(L_1\)与\(L_3\)的关系。
在(一)中我们已经证明了S从入口到相遇只走了\(L-L_2<L\)步,即小于1圈。
由于快指针走过的路程是慢指针的2倍: \[L_1+L_2+nL+2(L-L_2)=2(L_1+L-L_2)\] 即:
\[L_1=L_2+nL\] 又\(L_3=L-(L-L_2)=L_2\),故有: \[L_1=nL+L_3\]
n表示S第一次到达入口时,快指针已经绕了\(n\)圈。
也就是说:设两个指针\(p_1, p_2\),\(p_1\)指向链头,\(p_2\)指向相遇点,每次都走一步,则两指针必在环的入口相遇。
通俗理解:\(p_1\)指针先走\(L_3\)步,此时\(p_1\)距离环入口还有\(L_1-L_3=nL\)步,同时\(p_2\)也走了\(L_3\)步,刚好到环入口。接着\(p_1\)继续走\(nL\)步,\(p_2\)开始绕环\(n\)圈,必相遇。
1 | //单链表定义 |