算法学习记录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 相遇,正是环的入口!✅


相关推荐
睡一觉就好了。4 分钟前
快速排序——霍尔排序,前后指针排序,非递归排序
数据结构·算法·排序算法
Tansmjs33 分钟前
C++编译期数据结构
开发语言·c++·算法
金枪不摆鳍33 分钟前
算法-字典树
开发语言·算法
丝斯201137 分钟前
AI学习笔记整理(67)——大模型的Benchmark(基准测试)
人工智能·笔记·学习
diediedei39 分钟前
C++类型推导(auto/decltype)
开发语言·c++·算法
whale fall1 小时前
2026 年 1-3 月雅思口语完整话题清单(1-4 月通用最终版)
笔记·学习
独断万古他化1 小时前
【算法通关】前缀和:从一维到二维、从和到积,核心思路与解题模板
算法·前缀和
xian_wwq1 小时前
【学习笔记】对网络安全“三化六防挂图作战”的理解与思考
笔记·学习·三化六防
loui robot1 小时前
规划与控制之局部路径规划算法local_planner
人工智能·算法·自动驾驶
格林威1 小时前
Baumer相机金属焊缝缺陷识别:提升焊接质量检测可靠性的 7 个关键技术,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·视觉检测·堡盟相机