Leetcode 142. 环形链表 II

244 阅读5分钟

原题链接: 142. 环形链表 II - 力扣(Leetcode)

tag: 双指针, 链表.

在阅读本文前, 请先阅读如下一篇题解.

Leetcode 141. 环形链表 - 掘金 (juejin.cn)

一. 题目

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

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

不允许修改 链表.

二. 题解

141. 环形链表 - 力扣(Leetcode) 一题中, 我们已经知道如何判断一个链表是否带环.

接下来我们需要判断的是如果链表有环, 如何找到这个环形链表的入口节点.

假设头节点和环形链表入口节点之间的距离为 x, 环形链表入口节点和快慢指针相遇节点之间距离为 y, 快慢指针相遇节点和环形链表入口节点之间距离为 z.

image.png

快慢指针相遇时, slow 指针走的距离为 x + y , fast 指针走的距离为 x + y + n (y + z) .

n 为 fast 指针在环内遇到 slow 指针前所走的圈数, (y + z) 为环的长度.

因为 fast 指针一次走两步, slow 指针一次走一步, 所以 fast 指针走过的距离 = slow 指针走过的距离 * 2, 即: x + y + n (y + z) = (x + y) * 2 .

因为要找到环形链表的入口节点, 即要确定头节点到环形链表入口节点的距离(x), 所以用其他的字母表示 x, 即: x = (n - 1) * (y + z) + z. 这里的 n 必定是大于等于 1 的, 因为 fast 指针至少要走一圈才能遇到 slow 指针.

当 n 为 1 的时候, 上述公式化为 x = z .

这就意味着, 一个指针从头节点开始移动, 一个指针从相遇节点开始移动, 两个指针每次走一步, 会在环形链表入口节点相遇.

也就是说, 在头节点处, 定义一个 p1 指针, 在快慢指针的相遇节点处, 定义一个 p2 指针. 让 p1p2 同时开始移动, 每次移动一个单位长度, p1p2 相遇的地方就是 环形链表的入口节点 .

当 n 大于 1 的时候, 一样可以通过上述方法找到环形链表的入口节点, 只是 p2 指针在环中多转了 (n - 1) 圈, 再遇到 p1, p1p2 相遇的地方仍旧是 环形链表的入口节点 .

对于本题采用 双指针 的方法, 定义慢指针 slow 指向链表的头节点 head, 定义快指针 fast 指向链表的头节点 head. 慢指针 slow 一次走一步, 快指针 fast 一次走两步.

当慢指针 slow 进环时, 快指针已经指向环中的某个节点, 从此刻开始, 快指针 fast 开始追击慢指针 slow. 由于快指针 fast 一次走两步, 慢指针 slow 一次走一步, 每循环一次该步骤快慢指针之间的距离缩小一步, 最终快指针一定会追上慢指针, 即 slow == fast.

当快慢指针相遇后, 定义 p1 指针从头节点开始移动, 定义 p2 指针从 fast 指针与 slow 指针相遇节点开始移动, p1p2 同时开始移动, 每次移动一个单位长度, p1p2 相遇的地方就是 环形链表的入口节点 .

image.png

定义慢指针 slow, 快指针 fast.

ListNode* slow = head, * fast = head;

image.png

fastfast->next 不为空时.

fast != nullptr && fast->next != nullptr

image.png

慢指针走一步.

slow = slow->next;

image.png

快指针走两步.

fast = fast->next->next;

image.png

slow != fast

image.png

重复这一步骤.

image.png

重复这一步骤.

image.png

slow == fast, 快慢指针相遇.

image.png

在头节点处, 定义一个 p1 指针.

ListNode* p1 = head;

image.png

在快慢指针的相遇节点处, 定义一个 p2 指针.

ListNode* p2 = fast;

image.png

p1 != p2

image.png

更新 p1 指针.

p1 = p1->next;

image.png

更新 p2 指针.

p2 = p2->next;

image.png

p1 == p2, while 循环终止.

image.png

返回环形链表的入口节点.

return p1;

image.png

三. 复杂度分析

时间复杂度: O(N), N 是链表的长度, 快慢指针相遇前, slow 指针和 fast 指针走的次数都小于 N; 快慢指针相遇后, p1 指针和 p2 指针走的次数也都小于 N. 四个指针 slow, fast, p1, p2 走的总次数小于 4N.

空间复杂度: O(1).

四. 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head, * fast = head;    // 定义慢指针 slow, 快指针 fast
        while (fast && fast->next) {
            slow = slow->next;    // 慢指针走一步
            fast = fast->next->next;    // 快指针走两步

            if (slow == fast) {    // 当快慢指针相遇时
                ListNode* p1 = head;    // 定义 p1 指针从头节点开始移动
                ListNode* p2 = fast;    // 定义 p2 指针从 fast 指针与 slow 指针相遇节点开始移动
                while (p1 != p2) {    // 当 p1 指针和 p2 指针没有相遇时
                    p1 = p1->next;    // p1 指针走一步
                    p2 = p2->next;    // p2 指针走一步
                }    // 当 p1 指针和 p2 指针相遇时, while 循环终止
                return p1;    // 返回环形链表的入口节点
            }
        }
        return nullptr;    // 若给出的链表不是环形链表, 返回 nullptr
    }
};