LeetCode - 142. 环形链表 II

347 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。


原题:142. 环形链表 II

题目大意是:给定一个链表,找出入环的第一个节点,如果没有就返回空。

image.png

解题思路:

链表是单向的,如果一个链表中有环,就意味着,从第一个节点开始,顺着链表指针的方向一直遍历,如果能遍历到之前遍历过的元素,则有环,如果到结束都没有遍历到之前遍历过的元素,则代表没有环。

在 Java 中,可以用一个 Set 来保存遍历过的节点,每当遍历到一个节点,判断是否在 Set 中包含,第一次遍历到在 Set 中包含的节点,就是入环的节点。使用这种方法,可以很简单地得出链表中是否有环以及入环的节点。

但是题目中有个进阶要求,使用 O(1) 空间解决此题。上面的思路使用了 Set 来保存访问过的节点,因此空间复杂度是 O(n)

下面换一种思路。

在 LeetCode 上还有一道类似的题:

141. 环形链表

这道题比较简单,判断一个链表是不是有环,而不需要找出入环的点。这道题的经典解法是:定义快慢两个指针,快指针每次走两步,慢指针每次有一步,只要快指针还没有走到链表的结尾就一直走(实际上有环的链表是没有结尾的),如果快慢指针在某一个节点相遇了,就意味着链表有环。

我们可以顺着这个思路,继续思考:

image.png

上图来自 LeetCode,代表一个有环的链表,图中红色箭头代表链表的方向,紫色的点代表使用快慢指针判断链表是否有环的时候,快慢指针相遇的点。

我们假设在快慢指针相遇的时候,快指针已经已经绕环 n 圈,那么它走过的距离是:

a+n(b+c)+b=a+(n+1)b+nca+n(b+c)+b=a+(n+1)b+nc

而慢指针走过的距离是:

a+ba+b

(通过简单分析可知慢指针在相遇前不可能移动超过环的一圈)

因为快指针移动的距离是慢指针的两倍,因此可以得出:

a+(n+1)b+nc=2(a+b)a+(n+1)b+nc=2(a+b)

也就是:

a=c+(n1)(b+c)a=c+(n−1)(b+c)

因为 b+c 就是圆环的一圈,通过以上的等式可以得出,a 的长度相当于 c 的长度加上 n-1 个圆环的长度。

进一步分析可以得出,如果有两个指针分别从 a 和 c 出发,沿着链表的方向移动,那么它们会在入环点相遇。

因此,可以这样解决这个问题:先通过宽慢指针判断链表是否有环,如果有,记下相遇时的节点 hit,然后定义两个指针,一个指向 hit ,另一个指向头节点,让两个指针一相同的速度移动,它们相遇的位置,就是入环点。

最终代码:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode f = head, s = head, hit = null;
        while (f != null && f.next != null) {
            s = s.next;
            f = f.next.next;
            if (s == f) {
                hit = s;
                break;
            }
        }
        if (hit == null) {
            return null;
        }
        while (hit != head) {
            hit = hit.next;
            head = head.next;
        }
        return head;
    }
}