题目描述
给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null。不允许修改链表。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点(值为 2 的节点)
3 → 2 → 0 → -4
↑ |
└──────────┘
前置思考:
如果要完成O(1)的空间复杂度,那就要在141.环形链表的快慢指针解法的基础上优化,既然结果是具体的某一个节点,我第一时间就想着数学推导找到规律,所以问题在于怎么找到数学公式(能拿到的常量就是相遇时走了多少步,所以从步数下手)
变量定义
head 入环点 相遇点
|←--------- a ------------→|------------ b ------------→|
| |
|←--------- c ---------------|
(环的剩余长度)
a:head 到入环点的距离b:入环点到快慢指针相遇点的距离c:相遇点继续走回到入环点的距离- 环长 =
b + c
前置论证:慢指针一定在走完第一圈之前与快指针相遇
在正式推导之前,需要先证明这个前提,否则步数公式会更复杂。
设定时间节点:慢指针刚踏入环入口的那一刻
- 慢指针在环内位置
0(入口) - 快指针已经在环里,设其在环内位置
f,范围为0到(b+c-1)
追及分析:
- 每走一步,快指针追近慢指针 1 格(快走2步,慢走1步,差距缩小1)
- 快指针相对于慢指针距离为
(b+c-f)格(因为快指针只能往前走,需要绕过来追上) - 因此还需要追
b+c-f步
最坏情况: 当 f=1 时,需要追 b+c-1 步,慢指针最多走 b+c-1 步。
慢指针在环内步数≤b+c−1<b+c\text{慢指针在环内步数} \leq b+c-1 < b+c慢指针在环内步数≤b+c−1<b+c
最坏情况不是f=0的原因是:慢指针刚踏入入环点时快指针也在入环点,直接就不用追了,其实是最好情况。
结论:慢指针一定在走完第一圈之前就与快指针相遇 ✅
这保证了后续推导中慢指针的总步数可以简单写成 a + b。
解法:快慢指针 + 数学推导
思路
第一阶段 :用快慢指针找到相遇点。
第二阶段 :用数学公式推导出------从 head 和从相遇点同速出发的两个指针,会在入环点相遇。
数学推导
慢指针总步数: a + b(已由前置论证保证不超过一圈)
快指针总步数: 2(a + b)(始终是慢指针的两倍)
快指针路径描述: 走完入环前的路( + 在环里绕了 n 圈 + 再走 b 到达相遇点
2(a+b)=a+n(b+c)+b2(a+b) = a + n(b+c) + b2(a+b)=a+n(b+c)+b
化简:
2a+2b=a+nb+nc+b2a + 2b = a + nb + nc + b2a+2b=a+nb+nc+b
a=nb−b+nc=(n−1)b+nca = nb - b + nc = (n-1)b + nca=nb−b+nc=(n−1)b+nc
a=(n−1)(b+c)+c\boxed{a = (n-1)(b+c) + c}a=(n−1)(b+c)+c
关键解读:
(n-1)(b+c)是绕环走(n-1)整圈,等效于原地不动- 所以从相遇点 (!!!注意是假设的从相遇点出发,此时相对于入环点的距离为b)出发走
a步 = 绕了几圈后再走c步 = 恰好到达入环点(因为环长b+c) - 而入环点就是我们需要的答案,所以当A从head出发,B从相遇点出发的情况,步长为1,相遇的地方就是答案
结论:
| 指针 | 起点 | 走 a 步后的位置 |
|---|---|---|
| 指针 A | head | 入环点 |
| 指针 B | 相遇点 | 入环点(绕圈+走c步) |
两个指针从各自起点同时出发,同速行走,相遇处即为入环点!
完整代码
java
public ListNode detectCycle(ListNode head) {
// 第一阶段:找快慢指针相遇点
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
// 无环:while 正常结束(非 break 退出)
if (fast == null || fast.next == null) {
return null;
}
// 第二阶段:找入环点
ListNode meetNode = fast; // 从相遇点出发
ListNode newHead = head; // 从 head 出发
while (meetNode != newHead) {
meetNode = meetNode.next;
newHead = newHead.next;
}
return meetNode;
}
复杂度分析
- 时间复杂度: O(n),第一阶段最多走 O(n) 步找到相遇点,第二阶段最多走 O(n) 步找到入环点
- 空间复杂度: O(1),只使用了有限个指针变量
延伸知识点
1. 快慢指针的适用场景
| 场景 | 用法 |
|---|---|
| 检测链表是否有环 | 快慢指针相遇则有环(LeetCode 141) |
| 找环的入口 | 相遇后再用双指针(LeetCode 142) |
| 找链表中点 | 快指针到尾时,慢指针在中点 |
| 找倒数第 k 个节点 | 快指针先走 k 步,再同速走到尾 |