环形链表这道题,在LeetCode Hot 100里排在第二十五位。很多人第一次看到它,觉得不就是判断链表有没有环嘛,快慢指针背下来就完了。但实际上,这道题在面试里的地位远不止“背模板”这么简单:它考察的是你能否把一个抽象的运动模型讲清楚,能否从“会不会写”进阶到“为什么对”。我见过不少候选人能默写出代码,但被问到“为什么快指针每次走两步而不是走三步”“两个指针相遇时slow到底走了多少步”“怎么证明一定相遇”就卡壳。这篇文章我把这道题从头拆到尾,包括两个问法、三个解法、相遇原理、入口推导、代码实现,以及它背后的Floyd判圈思想能迁移到哪些题上,希望能帮你把这道题吃透。
1. 两个版本、三个解法:先把环形链表考什么摸清楚
1.1 判断环和找入口是两种难度
环形链表在LeetCode上有两个常见版本。第一个版本是第141题,只问链表是否存在环,返回布尔值;第二个版本是第142题,不仅要判断有环,还要返回环的入口节点。Hot 100里收录的是141题,但当你在面试中遇到这道题时,面试官大概率会接着追问142题,尤其在你说“我会用快慢指针”之后。
这两个版本的区别绝不只是“多返回一个节点”。141题可以用“改值法”“哈希表法”“快慢指针法”三种思路解,效果都还行;但142题对哈希表法的容忍度就低了,面试官想听的是你能否用O(1)空间完成入口定位。也就是说,141题是“验证你写过链表题”,142题是“验证你理解链表题的底层逻辑”。所以下面我会把两个版本一起讲,代码上也只是几行之差,但思考深度差了一个量级。
1.2 三种解法的暴力程度排序
先说结论:这道题的解法谱系非常清晰,从“修改链表本身”到“空间换时间”再到“常数空间”,正好是算法优化的三个典型路径。
第一种解法是“改值法”。遍历链表,把访问过的节点值改成某个特殊值(比如极大极小值),如果后续遍历又遇到这个特殊值,说明有环。这个解法能过OJ,但面试基本不能提,因为破坏了输入数据。不过它提醒了我们一个重要的直觉:判断是否“再次访问同一个节点”,本质上是做“去过的地方”的标记。
第二种解法是“哈希表法”。用HashSet记录每个节点引用,遍历时先查集合里有没有当前节点,有则说明回到了环上的某个节点。这个解法逻辑直观,时间复杂度O(n),空间复杂度O(n)。理论上完全正确,也是很多人的第一反应。但在面试场景下,面试官会追问一句“能不能不用额外空间”,这就引出了第三种解法。
第三种解法就是快慢指针,也叫Floyd判圈算法。两个指针从head出发,慢指针每次走一步,快指针每次走两步,如果链表有环,两个指针一定会相遇;如果无环,快指针会先到达null。这个解法的时间复杂度O(n),空间复杂度O(1),是这道题的最优解,也是本文重点展开的内容。
1.3 这道题真正的考点
如果只看“会不会写快慢指针”,那这道题只是一个记忆题。但面试官真正想看的是三件事:第一,你能否从哈希表方案自然过渡到常数空间方案,体现优化意识;第二,你能否严谨解释“为什么快慢指针一定会相遇”,而不是只说“因为快指针会追上慢指针”;第三,你能否在141题基础上推出环入口位置,体现数学推导能力。这三件事,分别对应了算法面试的“优化能力”“原理理解”“深度拓展”三个层次。下面我从原理层开始拆。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快慢指针为什么一定能抓住环:相对速度、步长选择与边界场景
2.1 相对速度才是理解核心
很多教程讲快慢指针,最喜欢说“快指针走得快,迟早追上慢指针”。这句话大方向没错,但不严谨。因为如果是直线链表,快指针直接走到头,根本不存在“追上”这回事。只有当链表里有环时,快指针进入环后会在环里绕圈,慢指针随后进入环,两个指针都困在环里,追及问题才成立。
更准确的理解方式是“相对速度”。想象你在环形操场上跑步,你跑得快,另一个人跑得慢,两人同向出发,只要一直跑下去,你快的人一定会从后面套圈追上慢的人。快慢指针在环里就是这么个场景:快指针相对慢指针的速度是“每单位时间接近一步”,不管环多大、起点位置多复杂,这个相对速度恒定为1,所以必然会相遇。
具体到代码里,慢指针每次走1步,快指针每次走2步,相对速度就是每步缩小1的距离差。假设入环前那段距离让慢指针进环时,快指针已经在环里多走了若干距离,但这个距离差是有限的,每迭代一次缩小1,最终一定缩小到0,也就是相遇。
2.2 为什么快指针每次走两步,而不是三步四步
这是一个高频追问。先说结论:步长差为1是保证“一定相遇”的充分条件,步长差为2(如走3步对1步)则可能出现跳过的情况,分析起来更复杂,还可能在某些条件下无法相遇。
我们严格推理一下:假设慢指针每次走1,快指针每次走k,那么快指针相对慢指针的速度是 (k-1)。只要 k-1 和环长 L 互质?不对,更准确的表述是:只要 k-1 是正整数,快指针相对慢指针会一步一步接近,但它每次“跨越”的间距是 (k-1),如果这个间距不是1,理论上有可能会跳过慢
