环形链表Ⅱ 142

22 阅读4分钟

力扣 142 环形链表Ⅱ

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

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

不允许修改 链表。

示例 1:

**输入:**head = [3,2,0,-4], pos = 1 **输出:**返回索引为 1 的链表节点 **解释:**链表中有一个环,其尾部连接到第二个节点。

示例 2:

**输入:**head = [1,2], pos = 0 **输出:**返回索引为 0 的链表节点 **解释:**链表中有一个环,其尾部连接到第一个节点。

示例 3:

**输入:**head = [1], pos = -1 **输出:**返回 null **解释:**链表中没有环。

提示:

  • 链表中节点的数目范围在范围 [0, 104] 内
  • -105 <= Node.val <= 105
  • pos 的值为 -1 或者链表中的一个有效索引

这道题跟着逻辑走思路很清晰,但实际上需要严谨证明和反复琢磨才能真正理解并独立解题

主要解决两点

  1. 判断链表是否成环
  2. 如果有环,那么环的入口在哪里

一. 是否成环?

这里可以采用双指针,分别定义快慢指针,均从头节点出发,其中 fast 每次移动两个节点,而 slow 每次移动一个节点,如果 fastslow 在移动途中相遇,说明该链表成环。

为什么快慢指针相遇就说明成环?

因为是快指针先入环,慢指针后入环,如果 fast 指针和 slow 指针相遇的话,一定是在环中相遇,在环内相遇就说明肯定成环了。

为什么有环一定会相遇?

本质上是因为两指针存在速度差,在环内绕环移动时,快指针一定会追上慢指针。这是因为 fast 走两步,slow 走一步,对 slow 来说 fast 是一点一点接近它的,所以两者一定会在环内某一节点处相遇。

画一个环,让 fast 在任意一个节点开始追赶 slow,会发现展开后都是下图这种情况:

环形链表Ⅱ  142_20260721_173447.png

此时,按照定义 fast往前走一次两步,slow走一次一步,两者就会在 3 处相遇。


二. 入口在哪里?

假设从头结点到入口节点的节点数为 x,入口节点到两指针相遇节点的节点数为 y,从相遇节点再到入口节点的节点数为 z , 如图所示:

环形链表Ⅱ  142_20260721_220506.png slow 指针走过的节点数为 x + yfast 指针走过的节点数为 x + y + n * (y + z) 其中 n 表示快指针追慢指针的过程中绕过的圈数。

由于快指针一次走两个节点,慢指针一次走一个,故快指针走过的节点数是慢指针的 2 倍。 也就是 2 * (x + y) = x + y + n * (y + z) ,两边消一个 x + y 化简得 x + y = n * (y + z)

要找到是环形入口节点,也就是 x,所以把 x 提出来放左边得 x = n * (y + z) - y 。右边提一个 y + z 出来化简得 x = (y + z) * (n - 1) + z

化简到这一步,不妨设环绕圈数 n1 ,即可得 x = z 。这也就是说,从头结点和相遇节点同时出发一个指针,两个指针每次只走一个节点, 那么当这两个指针相遇时所在处就是环形入口的节点

那么** n 如果大于 1** 是什么情况呢,其实这种情况和 n1 的时候是一样的,一样可以通过这个方法找到入口节点,只不过 index1 指针在环里多转了 (n-1) 圈,然后再遇到 index2 ,相遇点依然是环形的入口节点。

至此,我们完成了一开始提出的两个问题!


✅下面是完整力扣代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; 
            }
        }
        return nullptr;
    }
};