LeetCode LCR 022. 环形链表 II
题目描述


题意分析
判断单链表是否有环;若有环,返回从头节点出发第一次进入环的那个节点,没有环则返回空。入口是节点对象,不是它的值,值相同不表示指向同一节点。
不能修改节点之间的连接。链表可能为空,也可能单节点自环,或者头节点本身就是入口。需要进一步用常数额外空间完成,不能依赖集合保存全部访问历史。
解法:快慢指针判环并找入口
核心思路
[!blue]
先用两个指针判断有无环。
slow每轮走一步,fast每轮走两步,均从头出发。没有环时快指针会到达空;有环时两者进入环后,相对位置每轮改变一格,最多再经过一个环长就会重合。必须先移动再比较,因为初始时它们本来就指向同一个头节点,这不能作为有环的依据。快指针移动前同时检查自己和后继,保证一次走两步合法。
为什么相遇后能找到入口?设头到入口距离为
a,环长为L,相遇时慢指针共走了t步。快指针走了2t步,两者位于同一个环内节点,路程差t必须是环长的整数倍,即t % L == 0。相遇点相对入口的环内偏移是
(t - a) % L。从相遇点再走a步,偏移变成t % L = 0,恰好回到入口。同时从头出发的另一个指针走a步,也第一次到达入口。因此将一个指针放回头,另一个留在相遇点,让它们都每轮走一步即可。在走满
a步之前,从头出发的指针仍在非环前缀,另一指针始终在环内,不会提前相遇;头本身就是入口时,两者在第二阶段开始时已经相等。这一推导允许慢指针绕过整圈,不必假设第一次相遇前它只走了头到入口再到相遇点的最短距离。
解题步骤
- 将快慢指针都初始化为
head。- 只要
fast和fast.next都非空,慢指针走一步、快指针走两步。- 移动后比较节点引用;若相遇,令
answer = head,保留慢指针的位置。answer与慢指针同步走一步,第一次引用相等时返回该节点。- 第一阶段先走到链尾则返回空,表示无环。
代码实现
class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
// 先推进再判等,避免同起点在第一轮被误判为相遇。
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
// 相遇时慢指针总步数是环长整数倍,从头与相遇点同速走到入口。
ListNode answer = head;
while (answer != slow) {
answer = answer.next;
slow = slow.next;
}
return answer;
}
}
return null;
}
}
func detectCycle(head *ListNode) *ListNode {
fast, slow := head, head
for fast != nil && fast.Next != nil {
// 先推进再判等,避免同起点在第一轮被误判为相遇。
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
// 相遇时慢指针总步数是环长整数倍,从头与相遇点同速走到入口。
answer := head
for answer != slow {
answer = answer.Next
slow = slow.Next
}
return answer
}
}
return nil
}
复杂度分析
- 时间复杂度:$O(n)$,其中 $n$ 是可达的不同节点数。第一阶段进入环后至多再走一个环长相遇,第二阶段走头到入口的距离。
- 空间复杂度:$O(1)$,只使用固定数量的指针,不修改链表。
关键点总结
[!green]
- 快慢速度差保证有环时相遇,快指针到空则说明无环。
- 相遇时慢指针总路程是环长整数倍,这是入口定位的关键关系。
- 头与相遇点同步再走头到入口的距离,都落在入口。
- 两个阶段均按节点身份比较,不能比较节点值。
易错点总结
[!yellow]
- 初始指针相等不能判环,第一阶段必须先走再比较。
- 只检查
fast而不检查fast.next,无法保证两步跳转安全。- 第二阶段两个指针都走一步,不能继续沿用两倍速度。
- 路程推导要允许完整绕圈,不能把实际总步数一律当成最短路程。
- 用修改
next的方式标记访问,会破坏题目要求保留的链表结构。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 141. 环形链表 | 简单 | 先用快慢指针判断有环,本题还要根据相遇位置推导并寻找入环点。 |
| 287. 寻找重复数 | 中等 | 把数组下标跳转视为函数图,可复用找环入口的方法确定重复值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!