C++代码随想录刷题知识分享-----142.环形链表II
1. 题目要求
给定一个链表的头节点 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
或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题
2. 算法思想 —— 快慢指针 Floyd 判环算法
🧠 思路拆解:
-
第一阶段:判断是否有环
- 设置两个指针:
slow
每次走一步,fast
每次走两步。 - 若
fast
在某一时刻追上slow
(slow == fast
),说明链表有环。 - 否则,
fast
或fast->next
先到nullptr
,说明无环。
- 设置两个指针:
-
第二阶段:寻找环的入口节点
- 新建一个指针
cur
从链表头开始。 cur
和slow
每次都走一步,最终会在环的起点相遇。
- 新建一个指针
3. 完整注释版代码
class Solution {
public:ListNode *detectCycle(ListNode *head) {ListNode* slow = head;ListNode* fast = head;// 第一阶段:先判断链表是否有环(快慢指针)while (fast != nullptr && fast->next != nullptr) {slow = slow->next; // 慢指针走一步fast = fast->next->next; // 快指针走两步if (slow == fast) { // 有环,找到相遇点// 第二阶段:从 head 和相遇点出发,再次相遇即为入环点ListNode* cur = head;while (cur != slow) {cur = cur->next;slow = slow->next;}return cur; // 入环起点}}return nullptr; // fast 跑到了 null,无环}
};
4. 为什么相遇点到入环点距离 = 头到入环点距离?
4.1. 变量定义(统一符号)
L
:head 到入环点的距离(非环部分长度)C
:环的总长度(环的周长)x
:入环点到相遇点的距离(顺时针方向)k
:快指针比慢指针多绕的圈数m
:慢指针从入环点走到相遇点时走的距离,即x
D
:从相遇点再走C - x
就是回到入环点的距离
4.2. 数学推导(核心逻辑)
设快慢指针在某个时刻第一次相遇:
- 慢指针走了:
L + x
- 快指针走了:
L + x + k * C
(因为比慢指针多走k
圈环)
因为快指针每次走 2 步,慢指针走 1 步,所以:
2(L + x) = L + x + kC
化简得:
L + x = kC → L = kC - x
🔥 结论来了:
从相遇点再走 C - x
步会到入环点
从head 走 L = kC - x
步也会到入环点
所以:
一个指针从 head 出发,一个从相遇点出发,每次走一步,它们会在入环点相遇。
🔁 3. 图形辅助理解
链表结构如下(→
表示 next):
head → ... → (入环点) → a → b → c → ... → x↑ ↓←←←←←←←←←←←←←←
设相遇发生在环中的节点 x
,从入环点到 x
走了 x
步。
你从 head 出发到达入环点,也走了 kC - x
步。
- 慢指针再走
C - x
就回到入环点; - 而从 head 出发刚好也走了
kC - x
步; - 两者同时走,都会在入环点碰面。
🧪 4. 举个例子
假设链表如下:
head → A → B → C → D → E → → → →↑ ↓H ← G ← F
- 入环点是:
E
- 环:
E → F → G → H → E
(长度C = 4
) - 假设快慢指针第一次相遇在
G
,即入环点E
到相遇点G
的距离是x = 2
- 则:
L = kC - x = 1×4 - 2 = 2
- 也就是说,从 head 到
E
有两步 - 从
G
再走C - x = 2
步也回到E
- 两者同时走,都会在
E
相遇
根据 Floyd 判环算法推导,在第一次相遇后,从头节点和相遇点各起一指针,每次一步,它们将同时在入环点相遇。
原因是:
相遇前慢指针走了L + x
,快指针走了L + x + kC
,由快慢速比得出:
2(L + x) = L + x + kC → L = kC - x
,即从 head 到入环点的距离等于从相遇点到入环点的距离。
所以两个指针每次走一步,最终会同步到达入环点。
5. 时间与空间复杂度
指标 | 复杂度 |
---|---|
时间复杂度 | O(n) 最多遍历两次链表 |
空间复杂度 | O(1) 不使用额外数据结构 |
6. 扩展面试题 & 必会变体
问题 | 解法提示 |
---|---|
如何判断链表是否有环? | Floyd 判环算法 |
如何找出环的起始点? | 相遇后双指针同步走 |
环的长度是多少? | 相遇后在环中走一圈计数 |
如果链表有交点 + 有环怎么办? | 必须分环内交点、环前交点、多环链表等情况分析 |
7. 小结
知识点 | 一句话记忆 |
---|---|
Floyd 判环 | 快慢指针相遇即有环 |
入环点求法 | 相遇后设新指针从 head 出发,与 slow 同步走 |
空间优化 | 不使用哈希表,仅用指针操作 |
不允许修改链表结构 | 所以不能破坏 next 指针或用标记法 |