当前位置: 首页 > news >正文

链表“追及”问题终极指南:快慢指针三部曲

快慢指针算法三部曲:从入门到精通

第一部曲:热身 - 寻找链表中点(★★★★☆)

核心思想:快指针速度是慢指针的 2 倍,当快指针到达终点时,慢指针恰好位于中点。

python

class Solution:def middleNode(self, head: ListNode) -> ListNode:slow, fast = head, headwhile fast and fast.next:  # 快指针需能继续走两步slow = slow.next       # 慢指针走1步fast = fast.next.next  # 快指针走2步return slow

深度解析

  • 条件fast and fast.next的作用
    • fast is not None确保快指针本身存在,避免空指针异常;
    • fast.next is not None确保快指针有下一个节点,可安全移动两步。
  • 奇偶长度处理
    • 奇数长度(如1→2→3):快指针到3后,fast.next=None,循环停止,慢指针到2(中点)。
    • 偶数长度(如1→2→3→4):快指针到None,循环停止,慢指针到3(偏右中点)。
第二部曲:核心 - 判断链表是否有环(★★★★★)

核心思想:若链表有环,快指针终将在环内追上慢指针;若无环,快指针先到达终点。

python

class Solution:def hasCycle(self, head: ListNode) -> bool:slow, fast = head, headwhile fast and fast.next:slow = slow.nextfast = fast.next.nextif slow == fast:  # 相遇即有环return Truereturn False

关键证明

  • 若无环,快指针先到终点,返回False
  • 若有环,快指针比慢指针多跑一圈后必会相遇。例如:
    环长为C,快指针速度 2v,慢指针速度 v。进入环后,快指针相对慢指针的速度为 v,必然在C/v时间内追上。
第三部曲:高潮 - 寻找环的起点(★★★★★)

算法步骤

  1. 用快慢指针找到环内相遇点;
  2. 慢指针回表头,快慢指针同速移动,相遇点即为环起点。

python

class Solution:def detectCycle(self, head: ListNode) -> ListNode:slow = fast = head# 阶段1:找相遇点while fast and fast.next:slow, fast = slow.next, fast.next.nextif slow == fast:break# 无环判断if not fast or not fast.next:return None# 阶段2:找环起点slow = head  # 慢指针回表头while slow != fast:slow, fast = slow.next, fast.nextreturn slow

数学推导
设:

  • m = 表头到环起点的距离;
  • k = 环起点到相遇点的距离;
  • C = 环的周长。
    相遇时:
  • 慢指针路程:m + k
  • 快指针路程:m + k + nC(多跑n圈)。
    因快指针速度是慢指针 2 倍:

plaintext

m + k + nC = 2(m + k)  →  nC = m + k  →  m = nC - k

结论m等于从相遇点绕环n圈后后退k的距离,故表头和相遇点出发的指针同速移动会在环起点相遇。

随堂测验解析

问题 1:快指针速度 3 步,慢指针 1 步,慢指针最终位置?

  • 答案:链表长度的 1/3 处。
  • 推导:设链表长L,快指针到终点时走了L步(若L是 3 的倍数),慢指针走了L/3步。

问题 2:快慢指针首次相遇是否一定在环内?

  • 答案:是。若没进环,快指针始终领先慢指针,无法相遇;只有进环后,快指针才能从后方追上慢指针。

问题 3:第二阶段用快指针从相遇点出发是否正确?

  • 答案:正确。相遇时快慢指针在同一点,第二阶段慢指针回表头,快指针留在相遇点,同速移动必在环起点相遇。

你是否曾被链表问题搞得晕头转向?今天,我们将学习一个“降维打击”级别的技巧——。只需掌握这一招,你就能轻松破解一系列看似复杂的链表难题。快慢指针(又名“龟兔赛跑”算法)

我们将通过一个“三部曲”的形式,从热身到高潮,彻底征服它!

第一部曲:热身 - 寻找链表中点 (★★★★☆)

重要性评级:★★★★☆ (非常常见的面试题,是后续技巧的基础)
核心思想:快指针走两步,慢指针走一步。当快指针到达终点时,慢指针恰好在中间。

想象一下,在一条笔直的赛道上,兔子(快指针 'fas)的速度是(慢指针 'slo快慢)的两倍。当兔子跑到终点时,乌龟跑了多远?——正好是赛道的一半!

代码实现:

      class Solution:def middleNode(self, head: ListNode) -> ListNode:# 初始化:乌龟和兔子都从起点 head 出发slow, fast = head, head# 比赛开始!只要兔子还能往前跑,就继续while fast and fast.next:# 乌龟走一步slow = slow.next# 兔子跑两步fast = fast.next.next# 比赛结束,乌龟所在的位置就是中点return slow

深度解析:为何如此精妙?同时 Fast 和 Fast。

这是这个算法最优雅的地方,它完美地处理了链表长度为奇数和偶数的两种情况:

  • 奇数长度 (例如, 1->2->3->4->5):

    • 快 的路径: 。1 -> 3 -> 5

    • 当 指向快5 时,为fast.next (快速.下一个)没有,循环结束。

    • 此时 指向 ,正是中点。慢3

  • 偶数长度(例如,1->2->3->4):

    • 快的路径: `1 -> 3 ->。1 -> 3 -> 无

    • 当 'fa 指向 快3后,下一步 'fas会使快.next.next快变为 'N,循环结束。没有

    • 此时 指向 慢3,是两个中点中的后一个,符合 LeetCode 题目要求。

你的观察非常敏锐!是为了处理偶数情况,fast 不是 Nonefast.next 不是 None是为了处理奇数情况。两者结合,代码无懈可击!

第二部曲:核心 - 判断链表是否有环 (★★★★★)

重要性评级: ★★★★★ (经典中的经典,快慢指针的代表作)
在一个环形赛道上,速度不同的两人赛跑,跑得快的人终将从后面“套圈”追上跑得慢的人。核心思想:

如果链表有环,那么它就像一个“圆形操场”。快慢指针进入环后,就如同在操场上赛跑。由于 比 '快慢快,必将在某一时刻追上快慢。如果 顺利到达了终点 (快没有),那就说明这是一条“直路”,没有环。

代码实现:

      class Solution:def hasCycle(self, head: ListNode) -> bool: # -> bool 表示函数返回布尔值(True/False)slow, fast = head, headwhile fast and fast.next:slow = slow.nextfast = fast.next.next# 相遇了!说明赛道是环形的if slow == fast:return True# 兔子跑到了终点,说明没有环return False

这段代码几乎和找中点的一模一样,只是增加了一个“相遇”的判断。这就是优秀算法的特点:一个思想,多处应用。

第三部曲:高潮 - 寻找环的起点 (★★★★★)

重要性评级: ★★★★★ (的终极追问,深度考察你的理解)hasCycle 的
核心思想:这背后有一个绝妙的数学关系。

这是三部曲的高潮。如果链表有环,我们如何精确地找到环的入口?

算法分为两步:

  1. 第一阶段:和 `一样,用快慢指针找到环中的hasCycle 的相遇点

  2. 第二阶段:将**一个指针放回头节点 `,另一个指针留在相遇点。然后,一个指针放回头节点头两个指针都以相同的速度(一次一步)前进。它们下一次相遇的地方,就是环的起点!

为什么会这样?——揭秘背后的数学之美

别担心,这个推导很简单:

  • 设:

    • m = 从 到 环起点的距离头

    • k= 从环起点到相遇点的距离

    • C= 环的周长

  • 当它们相遇时:

    • 慢指针 走过的路程 =慢米 + 千

    • 快指针 'fast走过的路程 = 'm + k + n快m + k + n*C (比 多跑了慢n圈)

  • 因为 速度是快慢的两倍,所以 'dist_fast = 2 * dist_s:dist_fast = 2 * dist_slow
    m + k + n*C = 2 * (m + k)

  • 化简得到:
    n*C = m + k

  • 进一步变换:
    m = n*C - k

这个公式 'm = n*C就是魔法的关键!它告诉我们:m = n*C - k
从 到环起点的距离 (头m),等于。这等价于,一个指针从从相遇点继续走圈再后退步的距离nk一个指针从出发,另一个指针从相遇点出发,都走步,它们会在环的起点相遇!头m

代码实现:

生成的 python

      class Solution:def detectCycle(self, head: ListNode) -> ListNode:slow, fast = head, head# --- 第一阶段:找到相遇点 ---while fast and fast.next:slow = slow.nextfast = fast.next.nextif slow == fast:break # 相遇,跳出循环# 如果 fast 或 fast.next 是 None,说明 fast 走到了终点,没有环if not fast or not fast.next:return None# --- 第二阶段:寻找环的起点 ---# 将 slow 指针重新指向头节点slow = head# 两个指针以相同速度前进,直到再次相遇while slow != fast:slow = slow.nextfast = fast.next# 再次相遇点就是环的入口return slow

随堂测验 (检验你的掌握程度)

问题1: 在“寻找链表中点”的算法中,如果将快指针的速度改为每次走 3 步,慢指针仍然走 1 步。当快指针到达终点时,慢指针在什么位置?

答案: 慢指针会停在链表长度的 处。这个技巧可以推广,如果快指针速度是慢指针的 1/3k 倍,那么慢指针最终会停在 的位置。1/k

</详情>

问题2: 在“判断链表是否有环”的算法中,慢指针 和快指针 慢快 第一次相遇一定是在环内吗?为什么?

<details>
<summary>点击查看答案</summary>

答案: 是的,一定在环内。因为只有进入了环, 才有机会从后面追上 快慢。如果还没到环的入口, 只会离 快慢 越来越远,不可能相遇。相遇本身就证明了环的存在,并且相遇点必然在环上。

</详情>

问题3: 在“寻找环的起点”算法的第二阶段,我们让一个指针从 开始,另一个从相遇点开始,同速前进。如果我们将从相遇点开始的那个指针换成 头快(而不是在 后保持 break快 不动),结果还正确吗?

<details>
<summary>点击查看答案</summary>

答案: 完全正确。在 时,break慢 和 指向的是同一个节点(相遇点)。所以,在第二阶段的 快while 循环开始前, 被重置为 慢头,而 仍然停留在相遇点。代码 快while slow != fast 使用 作为从相遇点出发的指针是完全正确的,也是标准写法。快

</详情>

恭喜你完成了快慢指针三部曲的学习!这个看似简单的技巧,背后蕴含着巧妙的逻辑和数学之美。掌握它,你将在链表的世界里游刃有余!

详细版:

如何求链表的中点呢?

一 我们可以先通过遍历获取链表的长度n,然后再次遍历一次得到n/2,从而获得中间节点。

二 一次遍历成功,我们通过增加指针的量数从而减少遍历的次数,

这次我们放两个指针一个快一个慢。

每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点

class Solution:def middleNode(self, head: ListNode) -> ListNode:# 快慢指针初始化指向 headslow, fast = head, head# 快指针走到末尾时停止while fast is not None and fast.next is not None:  你们可能会想这里只有fast.next is not one不可以吗?为什么要带上fast is not none呢? nononono  如果链表的长度是偶数的话,会有两个中点,第二次循环时fast指针就会指向none,因为判定条件如果只有fast.next is not none的话就会报错,因为fast本身在的位置就是none,下一个就不是none所以会报错,但如果加上fast is not None就会避免这种情况# 慢指针走一步,快指针走两步slow = slow.nextfast = fast.next.next# 慢指针指向中点return slow

如何判断链表中是否有环?

同样也是利用快指针以及慢指针,如果fast能够顺利的走到链尾就证明没有环,相反的如果最后fast和slow相遇了就代表链表包含环。

很简单是上一个题目的套版

class Solution:def hasCycle(self, head: ListNode) -> bool:其中->bool的意思是最后函数返回的结果是TRUE或者False# 快慢指针初始化指向 headslow, fast = head, head# 快指针走到末尾时停止while fast is not None and fast.next is not None:# 慢指针走一步,快指针走两步slow = slow.nextfast = fast.next.next# 快慢指针相遇,说明含有环if slow == fast:return True# 不包含环return False

既然判断了是否存在环,那么我们就将这个难度稍微的往上面加一加,如果存在环那么如何确定这个环的起点呢?

class Solution:def detectCycle(self, head: ListNode):fast, slow = head, headwhile fast and fast.next:fast = fast.next.nextslow = slow.nextif fast == slow:break# 上面的代码类似 hasCycle 函数if not fast or not fast.next:   这串代码的含义是如果not fast——如果fast是空,或者fast.next是空则说明不包含环,直接返回none。# fast 遇到空指针说明没有环return None# 重新指向头结点slow = head # 快慢指针同步前进,相交点就是环起点while slow != fast:    如果slow不等于fast就让其一直循环下面的代码直到slow等于fast,因为之前fast的速度是slow的两倍所以,这时slow被fast套圈了,slow运动N而fast运动2n(n为链表长度,两个指针相遇的地方就是链表的起点)fast = fast.nextslow = slow.nextreturn slow

相关文章:

  • 自己怎么做短视频网站网络推广和网站推广
  • 广告开户百度热搜关键词排名优化
  • 网站ssl证书怎么做郑州百度seo关键词
  • 研磨材料 东莞网站建设成都网站快速排名提升
  • 图书馆网站建设调查问卷国家免费培训机构
  • 建立网站英文龙岗网络公司
  • 汉字编码之GBK编码详解
  • 数据结构 顺序表与链表
  • Spring Cloud Ribbon核心负载均衡算法详解
  • SDC命令详解:使用write_sdc命令进行输出
  • 高等数学》(同济大学·第7版)第七章 微分方程 第五节可降阶的高阶微分方程
  • Feign源码解析:动态代理与HTTP请求全流程
  • Azure虚拟机添加磁盘
  • 企业级RAG系统架构设计与实现指南(Java技术栈)
  • 深入学习入门--(一)前备知识
  • Java基础黑马进阶综合考试
  • 网络安全漏洞扫描是什么?如何识别目标进行扫描?
  • 理解epoll:水平触发与边沿触发
  • (C++)vector数组相关基础用法(C++教程)(STL库基础教程)
  • 《从0到1:C/C++音视频开发自学指南》
  • 多个 Job 并发运行时共享配置文件导致上下文污染,固化 Jenkins Job 上下文
  • 家用存储怎么选?NAS vS 硬盘柜,备份游戏素材与照片谁更合适?
  • vue2 使用el-form中el-form-item单独绑定rules不生效问题
  • 51c嵌入式~CAN~合集2
  • 学习日记-spring-day37-6.25
  • C++11原子操作:从入门到精通