原题链接: 142. 环形链表 II - 力扣(Leetcode)
tag: 双指针, 链表.
在阅读本文前, 请先阅读如下一篇题解.
Leetcode 141. 环形链表 - 掘金 (juejin.cn)
一. 题目
给定一个链表的头节点 head
, 返回链表开始入环的第一个节点. 如果链表无环, 则返回 null
.
如果链表中有某个节点, 可以通过连续跟踪 next
指针再次到达, 则链表中存在环. 为了表示给定链表中的环, 评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos
是 -1
, 则在该链表中没有环. 注意:pos
不作为参数进行传递, 仅仅是为了标识链表的实际情况.
不允许修改 链表.
二. 题解
在 141. 环形链表 - 力扣(Leetcode) 一题中, 我们已经知道如何判断一个链表是否带环.
接下来我们需要判断的是如果链表有环, 如何找到这个环形链表的入口节点.
假设头节点和环形链表入口节点之间的距离为 x, 环形链表入口节点和快慢指针相遇节点之间距离为 y, 快慢指针相遇节点和环形链表入口节点之间距离为 z.
快慢指针相遇时, 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
指针. 让 p1
和 p2
同时开始移动, 每次移动一个单位长度, p1
和 p2
相遇的地方就是 环形链表的入口节点 .
当 n 大于 1 的时候, 一样可以通过上述方法找到环形链表的入口节点, 只是 p2
指针在环中多转了 (n - 1) 圈, 再遇到 p1
, p1
和 p2
相遇的地方仍旧是 环形链表的入口节点 .
对于本题采用 双指针 的方法, 定义慢指针 slow
指向链表的头节点 head
, 定义快指针 fast
指向链表的头节点 head
. 慢指针 slow
一次走一步, 快指针 fast
一次走两步.
当慢指针 slow
进环时, 快指针已经指向环中的某个节点, 从此刻开始, 快指针 fast
开始追击慢指针 slow
. 由于快指针 fast
一次走两步, 慢指针 slow
一次走一步, 每循环一次该步骤快慢指针之间的距离缩小一步, 最终快指针一定会追上慢指针, 即 slow == fast
.
当快慢指针相遇后, 定义 p1
指针从头节点开始移动, 定义 p2
指针从 fast
指针与 slow
指针相遇节点开始移动, p1
和 p2
同时开始移动, 每次移动一个单位长度, p1
和 p2
相遇的地方就是 环形链表的入口节点 .
定义慢指针 slow
, 快指针 fast
.
ListNode* slow = head, * fast = head;
当 fast
和 fast->next
不为空时.
fast != nullptr && fast->next != nullptr
慢指针走一步.
slow = slow->next;
快指针走两步.
fast = fast->next->next;
slow != fast
重复这一步骤.
重复这一步骤.
slow == fast
, 快慢指针相遇.
在头节点处, 定义一个 p1
指针.
ListNode* p1 = head;
在快慢指针的相遇节点处, 定义一个 p2
指针.
ListNode* p2 = fast;
p1 != p2
更新 p1
指针.
p1 = p1->next;
更新 p2
指针.
p2 = p2->next;
p1 == p2
, while
循环终止.
返回环形链表的入口节点.
return p1;
三. 复杂度分析
时间复杂度: 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
}
};