当前:LC141 · 环形链表 · 首次出现于 Day 10 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC141 · Linked List Cycle · 链表

环形链表:快慢指针,有环必追尾

无环时快指针先掉进空;有环时快的相对慢的每轮只靠近一格,圈有限,终会踩到同一节点。

两个指针都从头出发,slow 一次走 1 格,fast 一次走 2 格。无环则链表有尽头,fast 会先让自己或 Next 变成 nil。有环则两人最终都进圈;把慢的看成静止,快的相对速度恰好是 1,圈长有限,距离一格一格减到 0,必然相遇。时间 O(n),空间 O(1)。

时间 O(n)空间 O(1)结论先行 · 全文约 6 节
导读

这是 LeetCode 141. Linked List Cycle。大白话:给你一条单链表的头节点 head,判断里面有没有环。有环返回真,没有返回假。环的意思是某个节点的 Next 指回前面出现过的节点,顺着走会在一圈里打转。

主例四个节点:3 → 2 → 0 → −4,并且 −4 的 Next 指回 2。圈是 2 → 0 → −4 → 2,圈长 3,输出 true。对照无环短链 1 → 2 → 3,快指针会在走到空时停,输出 false。空链表和单节点不自环都没有环;单节点自己指自己,是长度为 1 的环。

第一反应是用集合记下见过的节点地址,再走一步先查「这个地址我来过吗」。正确,但额外空间和节点数成正比。缺的是一种不记历史也能发现「回到了原地」的办法。口号「快的跑两步、慢的跑一步」不够:必须说清为什么一定相遇,而不是永远差一格互相错过。

最朴素:走过的节点做标记

哈希集合补的是「我见过这个地址吗」。从 head 出发,每到一个节点先查集合:在,就是有环;不在,把它放进去再走 Next。走到 nil,就是没环。主例会在第二次走到值为 2 的那个节点时发现地址重复。

这套做法时间 O(n)、空间 O(n),能当对照,不是这道题想逼出来的写法。给节点打访问标记同样能做,但题目往往不让你改节点结构,改完还要改回去,脏。

进阶要求空间 O(1)。于是缺口从「怎么记住见过的节点」换成「怎么用相对位置代替历史」。集合记住的是地址清单;快慢指针记住的是两人的间距。两种记忆回答同一句「有没有回到过」。

空间哈希法正确,但把题目换成了「怎么记住见过的地址」。快慢指针用相对速度 1 代替那张表,空间压到两个指针。

快慢指针:每轮缩小 1 步距离

没有环的链表有一个明确的尽头:某个节点的 Next 是 nil。只要有人跑得更快,这个人会先碰到尽头。这是无环时的可靠信号。有环时没有尽头。两个速度不同的人一旦都进了圈,问题变成跑道上的追逐。

把慢的看成静止,快的相对慢的每轮只多走 1 格(2 − 1 = 1)。相对速度是 1,不是 2。圈的周长是有限整数,初始距离也是整数,每轮距离减 1,减到 0 就是同一格。不会「永远差一格交错而过」:相对位移是 1,下一次要么仍落后,要么正好重叠,不会跳到对方身后却没踩到对方。步长取 2 而不是 3,就是为了让相对速度恰好为 1,分析最干净。

两个指针都从头出发。规则是先移动再比较——出发时两人都在头上,这是起点重合,不报有环。每走完一轮,先问 fast 还能不能再走两步:它自己是空,或者它的 Next 是空,说明前面没有两格可走,返回假。若还能走,slow 走 Next,fast 走 Next.Next,再问是不是同一个节点。是,返回真。比较的是节点地址,不是 Val。

主例给节点起地址:n3 → n2 → n0 → n-4,且 n-4.Next = n2。第 1 轮慢到 n2、快到 n0;第 2 轮慢到 n0、快到 n2;第 3 轮慢走 1 格到 n-4,快走 2 格也到 n-4,相遇。慢一共走了 3 格,快一共走了 6 格,快比慢多走的 3 格正好是圈长的一倍。对照无环 1 → 2 → 3:第 1 轮慢到 2、快到 3;第 2 轮快要从 3 走两格,3.Next 是空,循环条件失败,返回假。无环的信号是「前面不够两格」,不是「两人还没碰上」这句空话。

fast 每轮比 slow 多走 1 步,环内必相遇
3
2
0
-4
绕圈
slow 在 3,fast 在 2

Go:快慢指针

solution.goGo
func hasCycle(head *ListNode) bool {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast { return true }
}
return false
}

1slow 与 fast 同起点。出发时相等不代表有环,必须先移动再比较。

2循环条件 fast != nil && fast.Next != nil 保证读 Next.Next 时两级指针都在;过不了检查,说明有尽头。

3每轮 slow 走一格、fast 走两格,相对速度为 1。相遇看的是同一个节点,不是 Val 相等。

4fast 前面不够两格则返回 false。空链表、单节点不自环都落在这一个出口。

总结

无环时快指针先掉进空;有环时相对速度为 1,圈有限,终会踩到同一节点。

  • 哈希用历史地址证明「回到过」,空间线性。快慢指针用相对位置证明同一件事,空间两个指针。
  • 相对速度必须是 1:每轮靠近一格,不会跳过。步长取 3 时相对速度为 2,偶数圈长可能错过,证明变脏。
  • 本题只回答有没有环。若还要入环的第一个节点,相遇后再把一个指针放回头,一次走一格,那是 LC142。
同族题目
LC142环形链表 IILC287寻找重复数(快慢指针思想)LC876链表的中间结点