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


相关推荐
C雨后彩虹4 小时前
任务最优调度
java·数据结构·算法·华为·面试
晓幂5 小时前
【2025】HECTF
笔记·学习·web安全
少林码僧5 小时前
2.31 机器学习神器项目实战:如何在真实项目中应用XGBoost等算法
人工智能·python·算法·机器学习·ai·数据挖掘
钱彬 (Qian Bin)5 小时前
项目实践15—全球证件智能识别系统(切换为Qwen3-VL-8B-Instruct图文多模态大模型)
人工智能·算法·机器学习·多模态·全球证件识别
慕云紫英6 小时前
基金申报的一点经验
学习·aigc
微露清风6 小时前
系统性学习C++-第十八讲-封装红黑树实现myset与mymap
java·c++·学习
宝贝儿好6 小时前
【强化学习】第六章:无模型控制:在轨MC控制、在轨时序差分学习(Sarsa)、离轨学习(Q-learning)
人工智能·python·深度学习·学习·机器学习·机器人
大、男人6 小时前
python之asynccontextmanager学习
开发语言·python·学习
Niuguangshuo6 小时前
EM算法详解:解密“鸡生蛋“的机器学习困局
算法·机器学习·概率论
a3158238066 小时前
Android 大图显示策略优化显示(一)
android·算法·图片加载·大图片