Skip to the content.

找到链表中的环


Contact me:

Blog -> https://cugtyt.github.io/blog/index
Email -> cugtyt@qq.com
GitHub -> Cugtyt@GitHub


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?


首先如何确认链表有环?有环代表经过一段后移回到环的起点。

如果是循环链表,那么很简单,只要用一个指针向后移动,如果回到起点,那么就是环。但是问题是我们不知道环的起点在哪,一个粗暴的解法就是从头开始对每个节点都进行一次上述步骤,但是问题在于如果当前节点不是环的起点,那就无法结束。另外的想法就是借助外部空间,如果我们建立一个记录本,对每个节点记录所指向的节点,那么在一定时间内的确可以发现环的起点,当然这是一个解法。

但是如果不能借助空间呢?那就必须出现起点和终点的记录,试想如果我们使用两个指针,一个快,一个慢,快的一次走两步,慢的一次走一步,他们会相遇吗?考虑最简单的一个环的情况,如果我们将环展开,也就是说在环内走一圈就相当于在直线上展开那段距离:

那么环外距离设为s,慢指针走过的距离设为n,则快指针走过的距离就为2n:

指针间距离n就是走过的整数个环的距离,说明肯定是可以相遇的。

那么如果将环的起点返回呢?图中我们可以看到起点不是相遇的位置,但是我们已经有了起点的信息,为什么呢?

n是整数个环的距离,那么慢指针此时与环起点的距离是n-s,如果再前进s步,就回到了环的起点!那怎么走s步呢,我们不知道s,我们只需要让另外的头指针与慢指针一起每次走一步,刚好s步的时候再次和慢指针相遇!!!此时就是环起点位置了!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
ListNode *detectCycle(ListNode *head) {
    if (!head || !head->next) {
        return NULL;
    }
    ListNode* slow = head;
    ListNode* fast = head;
    do  {
        if (!fast->next) {
            return NULL;
        }
        slow = slow->next;
        fast = fast->next->next;
    } while (fast && slow != fast);
    if (!fast) {
        return NULL;    
    }
    // 第二个循环
    fast = head;
    while (slow != fast) {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

好的我们已经很好的理解了算法,提醒下我在图中简化了情况:

多个环怎么办?

因为沿着头节点走只会有一条路径,因此我们肯定会找到确定的环,那些问题的结果也是退化为这里的模型,当然这里没法找多个环的起点。

慢指针不一定是在第一个环停下的!

图中我把慢指针停在了第一个环,为了便于画图,当然可能是在后面的环相遇的。