[路飞]_环形链表II

1,664 阅读2分钟

题目描述

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

leetcode-142 环形链表II

解题思路一(Map)

如果一个链表不是环形链表,那么沿着链表进行遍历,最终会遍历到 null 节点的位置

如果一个链表存在环,那么在遍历到环的尾部节点(如下图的 -4 节点,只是做个比喻,环没有头尾之分)的时候,下一个位置会指向环的入口位置(如下图的 2 节点,也就是第二次遍历到 2 节点的时候)
因此,我们可以利用这个性质来进行解题

1639299567(1).png

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    if (!head || head.next === null) return null

    const mapper = new Map()
    let p = head
    while(p) {
        if (!mapper.has(p)) {
            mapper.set(p, p)
            p = p.next
        } else {
            return p
        }
    }
    return null
};

此处由于需要查找节点存放的位置,所以使用 map 结构会比使用数组结构的效率高

解题思路二(快慢指针法)

快慢指针法是同时设置两个指针,每次快指针比慢指针多走一步,如果两个指针能够再次相遇,说明这个链表有环,这个已经在[路飞]_环形链表说过了

从下图中我们可以看到 p、q自头节点之后第一次相遇的节点在 i 节点,而 i 节点到入环点的距离和头节点到入环点的距离是相等的

这不是巧合,而是可以验证的。
如图所示,假设 a -> c的距离是 x,c -> i的距离是 y,i -> c的距离是 z,由于相遇时 q 指针走的距离是 p 指针的两倍,会有这样的等式成立
2 * (x + y) = x + 2y + z,即 x = z

根据这个特性,我们可以编码解决这个问题

1639301474(1).png

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    if (!head || head.next === null) return null

    let p = q = head
    do {
        p = p.next
        q = q.next.next
    } while (p !== q && q && q.next)

    // 如果不是因为相遇退出循环,则表示不存在环
    if (p !== q) return null
    // 两个指针分别从头节点和相遇节点开始遍历,相遇点即为入环点
    p = head
    while (p !== q) {
        p = p.next
        q = q.next
    }
    return p
};