第4章|链表
Unit 08 · 环形链表 II:从相遇点定位环入口
第一次相遇只证明有环,
第二次相遇才定位入口。
Unit 07 已经解决“是否存在环”。本单元继续追问:不使用额外集合,怎样从快慢指针的第一次相遇点,精确返回环开始的节点?关键不是记住“再放一个指针”,而是理解两段距离为什么相等。
题目要求
给定链表头节点 head,若链表中存在环,返回环开始的节点;若不存在环,返回 None。评测中的 pos 只用于描述尾节点连接到哪个位置,不会作为函数参数传入。
复杂度目标
- 时间复杂度:
O(n); - 额外空间复杂度:
O(1); - 比较节点身份,而不是节点值。
示例
输出:返回值为 2 的节点对象
解释:尾节点连接到索引 1 的节点。
输出:返回值为 1 的头节点。
输出:None
本页主模型
第一阶段用 Floyd 快慢指针找到环内相遇点;第二阶段令 finder = head,再让 finder 与 slow 每轮各走一步,最终在环入口相遇。
与 Unit 07 的直接联系
第一次出现 slow is fast 就能返回 True。答案只需要布尔值。
第一次相遇不能直接返回。需要恢复一个从 head 出发的指针,再进行第二次同步移动。
从 head 沿 next 第一次进入循环区域的节点。它不一定等于快慢指针第一次相遇点。
第一阶段中 slow 与 fast 引用同一节点的位置。它只证明存在环,并提供第二阶段的起点。
从 head 到环入口需要走的边数。无环前缀也常称为链表的“尾部”或非环部分。
环中节点数量。若相遇点距离入口还需 c 步,则 head 到入口的距离与 c 在模 λ 意义下相等。
迁移题:LeetCode #287《寻找重复数》
把数组解释为一条隐式链表:下标是状态,nums[index] 是唯一后继。迁移目标不是背诵另一段 Floyd 代码,而是证明为什么“重复数字”恰好对应隐式环入口。
LeetCode #287|寻找重复数
打开 LeetCode 官方原题 ↗题目要求
给定包含 n+1 个整数的数组 nums,每个整数都在 [1,n] 中。数组中只有一个整数重复出现,但它可能出现两次或多次。返回这个重复整数。
官方示例与约束
[1,3,4,2,2] → 2[3,1,3,4,2] → 3[3,3,3,3,3] → 3nums.length = n+1,1 ≤ n ≤ 10⁵- 不能修改数组,只使用
O(1)额外空间。
读题检查 1|以下哪种做法违反题目约束?
读题检查 2|“只有一个重复整数”意味着什么?
[3,3,3,3,3]。节点对象是状态;node.next 给出唯一后继;环入口是从 head 首次进入循环的位置。
数组下标是状态;nums[index] 给出唯一后继;重复数字是两个或更多下标共同指向的入口。
| 结构角色 | #142 | #287 |
|---|---|---|
| 起点 | head | 下标 0 |
| 一步转移 | node.next | nums[index] |
| 第一次相遇 | 环内 meeting | 隐式环内 meeting |
| 第二阶段重置 | finder = head | finder = 0 |
| 入口含义 | 环入口节点 | 重复整数 |
先点击阶段,构造完整处理顺序。
只填写四个语义槽位
每个槽位代表一个算法责任,而不是机械拆成八个零散空格。
slow = nums[0]
fast = nums[nums[0]]
while slow != fast:
slow = nums[slow]
fast = nums[nums[fast]]
finder = 0
while finder != slow:
finder = nums[finder]
slow = nums[slow]
return finder
官方样例轨迹|nums = [1,3,4,2,2]
独立重写|不再提供占位符或完整答案
完成样例轨迹后,用自己的变量名重写可提交代码。检查器只验证关键结构。
可继续复看距离推导、无环轨迹与 #287 官方样例,不会自动跳转。