环形链表是链表中的一类特殊问题,它和链表反转一样,有着相对恒定的解题思路和适当的变体。如果你对它的特性和解法没有预先的了解和把握,那么前期的推导可能会花去你大量的时间。反过来看,只要我们能够掌握其核心思路,那么不管它怎么变化,大家都能在瞬间找到解题的“抓手”、进而给出正确的解答。

# 环形链表基本问题——如何判断链表是否成环?

⚡ 30 秒速记

  • 能改结点:沿路给结点打 flag,再碰到打过标记的就是有环
  • 不能改结点:Floyd 快慢指针,slow 一步、fast 两步,相遇就有环
  • fast 或 fast.next 先到 null 就是无环
  • 比的是结点引用 slow === fast,不是 val
  • Floyd:O(n) 时间、O(1) 空间;Set 记引用更直观但 O(n) 空间

判环最直观的是边走边给结点做标记,再碰到标记过的就说明绕回来了;但这会改输入,所以我一般用 Floyd 快慢指针。 想象操场跑圈,跑得快的人迟早会从后面追上跑得慢的。slow 每次走一步,fast 每次走两步,有环的话进了环之后 fast 每轮追近一步,一定会撞上;没环的话 fast 会先走到 null。比较时一定比引用,两个结点值一样不代表是同一个结点。

let slow = head, fast = head
while (fast && fast.next) {
  slow = slow.next
  fast = fast.next.next
  if (slow === fast) return true
}
return false

回答参考:“我先确认不能修改节点。然后用 Floyd 判环:快慢指针进入有限长度的环后,相对位置每轮前进一格,所以必然相遇;若快指针抵达 null,则无环。”

真题描述:给定一个链表,判断链表中是否有环。

示例 1:

输入:[3,2,0,4](链表结构如下图) 输出:true

解释:链表中存在一个环

思路解读

其实链表成环的特征非常明显,大家可以结合一个现实中的例子来理解:

假如现实中有一个长跑爱好者李雷,这货很狂,他立了一个 flag,说要徒步环游世界:

地球的周长围出来的这个圆,它就是一个“环”。李雷现在就想围着这个环跑上一圈,说他狂,他也没那么狂——他觉得自己最多跑一圈,为了防止自己跑过界,他决定在出发的地方立一个 flag:

这样,不管李雷走完这个环用了多少年,世事如何变迁,只要他的 flag 还没有倒,那么李雷就一定能回到自己梦开始的地方:)。

换个角度看:只要李雷在闷头前进的过程中,发现了 flag 的存在,那么就意味着,李雷确实走了一个环。毕竟若这是一条线,他将永远无法回到起点。

回到链表的世界里,也是一个道理。一个环形链表的基本修养,是能够让遍历它的游标回到原点

从 flag 出发,只要我能够再回到 flag 处,那么就意味着,我正在遍历一个环形链表。

我们按照这个思路来做题:

webapp
公众号
开发者导航
切换夜间模式
点击侧边栏上一篇
点击侧边栏下一篇
折叠侧边栏
收起全部