Skip to content

环形链表

统计信息:字数 4023 阅读9分钟

No.1 如何检测链表形成环?

给定一个链表,判断链表中是否形成环。

思路

思路一: 循环一遍,用 Set 数据结构保存节点,利用节点的内存地址来进行判重,如果同样的节点走过两次,则表明已经形成了环。

思路二: 利用快慢指针,快指针一次走两步,慢指针一次走一步,如果两者相遇,则表明已经形成了环。

可能你会纳闷,为什么思路二用两个指针在环中一定会相遇呢?

其实很简单,如果有环,两者一定同时走到环中,那么在环中,选慢指针为参考系,快指针每次相对参考系向前走一步,终究会绕回原点,也就是回到慢指针的位置,从而让两者相遇。如果没有环,则两者的相对距离越来越远,永远不会相遇。

接下来我们来编程实现。

方法一: Set 判重

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = (head) => {
    let set = new Set();
    let p = head;
    while(p) {
        // 同一个节点再次碰到,表示有环
        if(set.has(p)) return true;
        set.add(p);
        p = p.next;
    }
    return false;
}

方法二: 快慢指针

var hasCycle = function(head) {
    let dummyHead = new ListNode();
    dummyHead.next = head;
    let fast = slow = dummyHead;
    // 零个结点或者一个结点,肯定无环
    if(fast.next == null || fast.next.next == null) 
        return false;
    while(fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
        // 两者相遇了
        if(fast == slow) {
            return true;
        }
    } 
    return false;
};

No.2 如何找到环的起点

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

说明:不允许修改给定的链表。

思路分析

刚刚已经判断了如何判断出现环,那如何找到环的节点呢?我们来分析一波。

看上去比较繁琐,我们把它做进一步的抽象:

设快慢指针走了x秒,慢指针一秒走一次。

对快指针,有: 2x - L = m * S + Y -----①

对慢指针,有: x - L = n * S + Y -----②

其中,m、n 均为自然数。

① * 2 - ② 得:

L = (m - n) * S - Y-----③

好,这是一个非常重要的等式。我们现在假设有一个新的指针在 L 段的最左端,慢指针现在还在相遇处。

让新指针和慢指针都每次走一步,那么,当新指针走了 L 步之后到达环起点,而与此同时,我们看看慢指针情况如何。

由③式,慢指针走了(m - n) * S - Y个单位,以环起点为参照物,相遇时的位置为 Y,而现在由Y + (m - n) * S - Y即(m - n) * S,得知慢指针实际上参照环起点,走了整整(m - n)圈。也就是说,慢指针此时也到达了环起点。

结论

现在的解法就很清晰了,当快慢指针相遇之后,让新指针从头出发,和慢指针同时前进,且每次前进一步,两者相遇的地方,就是环起点。

编程实现

懂得原理之后,实现起来就容易很多了。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    let dummyHead = new ListNode();
    dummyHead.next = head;
    let fast = slow = dummyHead;
    // 零个结点或者一个结点,肯定无环
    if(fast.next == null || fast.next.next == null) 
        return null;
    while(fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
        // 两者相遇了
        if(fast == slow) {
           let p = dummyHead;
           while(p != slow) {
               p = p.next;
               slow = slow.next;
           }
           return p;
        }
    } 
    return null;
};

Last update: November 9, 2024