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 <= 105pos的值为-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:慢指针从入环点走到相遇点时走的距离,即xD:从相遇点再走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 指针或用标记法 |
