0%

Linked List Cycle

一、单链表是否有环

题目描述
快慢指针:若链表有环,则两指针必在将来某一时刻相遇:

  • 直观来看:本质上就是物理上的相对运动。快指针每次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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//单链表定义
struct ListNode{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL) {}
};

class Solution{
public:
ListNode* detectCycle(ListNode* head)
{
auto fast = head;
auto slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
break;
}
if(!fast || !fast->next) //无环,fast走到尽头
return nullptr;

slow = head; //一个指向链头,另一个指向相遇点
while(slow != fast)
{
fast = fast->next;
slow = slow->next;
}
return slow; //找到入口
}
};