题目链接:Linked List Cycle II

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

Follow up:

Can you solve it without using extra space?

这道题的要求是返回链表中开始出现环的位置。

Linked List Cycle的后继版本,需要进一步找出开始出现环的位置。

首先,同样需要设置两个指针fast和slow,初始值都指向头指针,slow每次前进一步,fast每次前进两步。如果存在环,则fast必先进入环,而slow后进入环,两个指针必定相遇(当然,fast先到达尾部为NULL,则为无环链表,返回NULL)。

此时为了寻找环的入口,将slow重新指向表头且仍然每次循环都指向后继,fast每次也指向后继。当fast和slow再次相等时,相等点就是环的入口。证明如下(摘自网络):

如果fast和slow相遇,那么在相遇时,slow肯定没有遍历完链表,而fast已经在环内循环了n圈(n>=1)。

假设 slow走了s步,则fast走了2s步(fast的步数还等于s加上在环上多转的n圈),设圈的周长为r,则:
    2s = s+nr
    s = nr

               x ____.<-相遇点
                /     \
    ___________/   r   \
         a     \       /
                \_____/

设整个链表长L,起点到环入口点的距离为a,环入口与相遇点距离为x,则
    a+x = s = nr
    a+x = (n-1)r+r = (n-1)r+L-a
    a = (n-1)r+(L-a-x)
其中(L-a-x)为相遇点到环入口点的距离。由此可知,从链表头到环入口点等于(n-1)循环内环加上相遇点到环入口点。于是可以从链表头和相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。

时间复杂度:O(n)

空间复杂度:O(1)

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
class Solution
{
public:
    ListNode *detectCycle(ListNode *head)
    {
        if(head == NULL || head -> next == NULL)
            return NULL;
        
        ListNode *s = head, *f = head;
        while(f != NULL && f -> next != NULL)
        {
            s = s -> next;
            f = f -> next -> next;
            
            if(f == s) // 说明有环
            {
                s = head; // 重新指向表头
                while(f != s)
                {
                    s = s -> next; // f每次走1步
                    f = f -> next; // s每次走1步
                }
                return f;
            }
        }
        
        return NULL;
    }
};