算法学习记录16——Floyd 判圈算法(环形链表 II)

最近在刷 LeetCode 的时候,遇到了一道经典题目:给定一个链表的头节点,返回链表开始入环的第一个节点;如果无环,就返回 null 。题目还特别强调:不能修改链表 。看题解了解到了 Floyd 判圈算法(快慢指针) ,感觉本质上还是数学

一开始我对这个算法只是有个模糊的印象------"快慢指针能判断有没有环",但具体怎么找到环的入口?为什么那样做是对的?这些细节我其实并不清楚。于是,我决定花点时间彻底搞懂它,并把整个思考过程记录下来。


🧩 问题理解

首先明确题意:

  • 链表可能有环,也可能没有。
  • 如果有环,我们要返回环的起始节点(即第一个被重复访问的节点)。
  • 不能修改链表结构(比如打标记、反转等都不行)。

所以,我们的目标是:只通过指针移动,在 O(1) 空间内找到环的入口


🔍 第一步:如何判断链表有环?

这一步我之前学过------用快慢指针(Floyd 判圈算法的第一阶段):

  • slow 每次走 1 步,fast 每次走 2 步。
  • 如果链表无环,fast 最终会走到 null
  • 如果有环,fast 一定会在某个时刻追上 slow(因为它们在环里跑,速度差为 1)。

这部分逻辑不难,代码也简单:

python 复制代码
slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    if slow == fast:
        # 有环!
        break
else:
    return None  # 无环

但问题来了:相遇点 ≠ 环的入口。那怎么从相遇点找到真正的入口呢?


🤔 第二步:为什么重置一个指针到 head 就能找到入口?

这是我最困惑的地方。网上很多教程直接说:"把 slow 放回 head,然后两个指针一起走,相遇就是入口。"

为什么?这背后一定有数学依据。

于是我画了个图,设了几个变量:

  • a:从链表头到环入口的距离。
  • b:从环入口到快慢指针第一次相遇点的距离。
  • c:从相遇点再回到环入口的距离(所以环总长 = b + c)。

slowfast 第一次相遇时:

  • slow 走了 a + b 步。
  • fast 走了 a + b + n(b + c) 步(n ≥ 1,因为它可能绕了多圈)。

又因为 fast 的速度是 slow 的两倍,所以路程也是两倍:

复制代码
2(a + b) = a + b + n(b + c)
=> a + b = n(b + c)
=> a = (n - 1)(b + c) + c

这个式子太关键了!

它说明:从 head 出发走 a 步,等于从相遇点出发走 c 步,再加上若干整圈

c 正好是从相遇点回到环入口的距离!

所以,如果我们:

  1. slow 重置到 head
  2. slowfast 都以每次 1 步的速度前进;

那么当 slow 走了 a 步到达入口时,fast 也刚好从相遇点走了 a 步------而根据上面的等式,这正好让它也到达入口!

于是,它们会在环的入口处相遇

💡 这一刻我恍然大悟:原来不是"碰巧",而是数学必然!


✅ 完整实现

结合以上分析,代码就清晰了:

python 复制代码
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

def detectCycle(head: ListNode) -> ListNode:
    if not head or not head.next:
        return None

    slow = fast = head

    # 第一阶段:检测是否有环
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    else:
        return None  # 无环

    # 第二阶段:找环的入口
    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next

    return slow  # 或 fast,此时两者相等

⚠️ 实现中的注意事项

在写代码时,有几个容易踩的坑,我总结如下:

  1. 边界情况要处理

    • 空链表(head is None)或只有一个节点且无环的情况,直接返回 None
  2. 循环条件别写错

    • 必须是 while fast and fast.next,否则 fast.next.next 会报错。
  3. 不要提前重置指针

    • 只有在确认有环(即 slow == fast)之后,才能重置 slow = head
  4. 空间复杂度是 O(1)

    • 整个过程只用了两个指针,完全符合题目"不允许修改链表"和"常数空间"的要求。
  5. 时间复杂度是 O(n)

    • 第一阶段最多走 2n 步,第二阶段最多走 n 步,总体线性。

🧪 举个例子验证一下

假设链表是:3 → 2 → 0 → -4 → 2(环),即 pos = 1

  • a = 1(head 到节点 2)
  • 环是 2 → 0 → -4 → 2,长度为 3
  • 快慢指针可能在 -4 相遇(b = 2, c = 1

根据公式:a = (n-1)*3 + 1,当 n=1 时,a = 1,成立。

重置 slow 到 head(3),然后:

  • slow: 3 → 2
  • fast: -4 → 2

在节点 2 相遇,正是环的入口!✅


相关推荐
星幻元宇VR10 小时前
VR漫游舱:让安全教育不再枯燥
科技·学习·安全·生活·vr
手握风云-10 小时前
优选算法的层序之径:队列专题
数据结构·算法·leetcode
世人万千丶10 小时前
Flutter 框架跨平台鸿蒙开发 - 气味图书馆应用
学习·flutter·华为·开源·harmonyos·鸿蒙
历程里程碑10 小时前
Protobuf总结
大数据·数据结构·elasticsearch·链表·搜索引擎
秋910 小时前
儒、释、道的根本区别:中国文化精神的三种维度
学习
破烂儿10 小时前
TMUX历史输出滚动查看全攻略(原生快捷键 + 鼠标配置优化)
服务器·学习·计算机外设
ZhiqianXia10 小时前
PyTorch 学习笔记(18) : lowering.py
pytorch·笔记·学习
ACGkaka_10 小时前
ES 学习(七)性能陷阱
大数据·学习·elasticsearch
我的xiaodoujiao10 小时前
API 接口自动化测试详细图文教程学习系列10--Requests模块2--举例说明
python·学习·测试工具·pytest
Yiyi_Coding10 小时前
一致性哈希算法
算法·哈希算法