一日一练:环形链表II

107 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

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

快慢指针

通过快慢指针可以判断当前链表是否有环,但是相遇位置在环的位置要怎们确定?

假设通过上述位置已经确定有环,如下图

image.png

起始位置为A,入环位置为B,快慢指针在环中相遇的位置为C,假设AB之间的距离为aBC之间的距离为bCB之间的距离为c,环的总长度为b + c。现在已有条件A已知为链表的头节点,C的位置也可以通过快慢指针确定,现在就是要确定B的位置或者说AB(距离为a)CB(c) 之间有什么关系?

  • 快慢指针在C相遇,根据快指针的速度为慢指针的2倍可以得出:慢指针走过的距离 (a + b) * 2 = 快指针走的距离(a + N * (b + c) + b) N为快指针在相遇之前在环内绕行的圈数,用公式表示:
(a+b)2=a+N(b+c)+b=>a=N(b+c)b (a + b) * 2 = a + N * (b + c) + b => a = N * (b + c) - b

假设环的总距离b + cL,则b可以表示为 L - c,所以上等式可以改写为

a=NL(Lc) a = N * L - (L - c)

则可以得出

a=(N1)L+c a = (N - 1) * L + c

根据上面的公式,可以看出,a的距离等于c的距离加上N - 1圈环的距离,那么如果一个指针pointAA出发,一个指针pointCC出发,同速前行,那么pointC绕行N - 1圈之后肯定会在B点与pointA相遇。

function detectCycle(head: ListNode | null): ListNode | null {
    if (head === null) return null
    // 这里快慢指针一定要从同时从head出发
    let slow: ListNode | null = head, fast: ListNode | null = head
    // 如果fast和slow相同,或者fast不存在退出循环
    while(slow && fast) {
        slow = slow.next
        fast = fast.next
        if (fast) {
            fast = fast.next
        }
        if (slow === fast) {
            break
        }
    }
    // fast如果为null,表明提前到达尾结点,表明没有环
    if (fast === null) {
        return null
    }    
    // 另起一个指针从头节点出发
    let cur = head
    while(cur && slow && cur !== slow) {
        cur = cur.next
        slow = slow.next
    }
    // 在入环点相遇,直接返回
    return cur
};

哈希表

从头节点开始将所有节点依次放入哈希表中,在哈希表中再次找到相同节点,表明当前有环,且入环节点肯定是第一个已存在的节点。

function detectCycle(head: ListNode | null): ListNode | null {
    let cur = head
    const set = new Set<ListNode>()
    while(cur) {
        if (set.has(cur)) {
            return cur
        }
        set.add(cur)
        cur = cur.next
    }
    return null
};