news 2026/4/30 23:51:10

【小白笔记】删除链表的倒数第N个节点与删除链表的中间节点,环形链表(两类双指针“滑窗与速度差”)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【小白笔记】删除链表的倒数第N个节点与删除链表的中间节点,环形链表(两类双指针“滑窗与速度差”)


这个问题是典型的**“双指针”**应用场景。它的巧妙之处在于:不需要先测量整个链表的长度,通过两个指针的“距离差”,只需一次遍历就能找到倒数第NNN个节点。


1. 核心思路:快慢指针(等距离滑动)

要删除倒数第NNN个节点,我们需要找到它的前驱节点(即倒数第N+1N+1N+1个节点)。

  1. 设置虚拟头节点 (dummy):依然是老规矩,为了处理删除“第一个节点”的情况,先创建一个dummy指向head
  2. 快指针 (fast) 先走:让fast先向后移动N+1N+1N+1步。此时,fast和慢指针slow(此时在dummy)之间隔了NNN个节点。
  3. 同速前进:让fastslow同时向后移动。当fast到达链表末尾(None)时,slow刚好停在倒数第N+1N+1N+1个节点上。
  4. “跨越”删除:执行slow.next = slow.next.next

原理直观图示:
假设链表长度为LLL,找倒数第NNN个,就是找正数第L−N+1L-N+1LN+1个。
fast先走NNN步,剩下还要走L−NL-NLN步到头。此时slow从头开始走L−NL-NLN步,正好停在倒数第NNN个的前面。


2. 代码实现 (Python)

classSolution:defremoveNthFromEnd(self,head:ListNode,n:int)->ListNode:# 1. 哨兵节点,处理删除头节点的情况dummy=ListNode(0,head)fast=dummy slow=dummy# 2. 快指针先走 n + 1 步# 这样快慢指针之间就会间隔 n 个节点for_inrange(n+1):fast=fast.next# 3. 快慢指针同时移动,直到快指针指向空whilefast:fast=fast.nextslow=slow.next# 4. 此时 slow 停在倒数第 n 个节点的前驱节点上# 直接跳过倒数第 n 个节点slow.next=slow.next.nextreturndummy.next

3. 为什么是走n + 1步?

这是一个容易出错的细节。

  • 我们要删除的是“倒数第NNN个”。
  • 在链表里删除一个节点,必须找到它的前一个
  • 如果你让fast先走NNN步,当fast走到None时,slow会停在“倒数第NNN个”上。这时候你没法删除它,因为你拿不到它的前驱节点。
  • 所以我们让fast多走一步(N+1N+1N+1步),这样slow就会正好落在“倒数第NNN个”的左边那位,也就是倒数第N+1个位置,这样slow的next就可以指向slow.next.next

4. 复杂度分析

  • 时间复杂度O(L)O(L)O(L),其中LLL是链表长度。我们只对链表进行了一次遍历。
  • 空间复杂度O(1)O(1)O(1)。只额外使用了两个指针。

总结:

这道题展示了双指针的一种常用技巧——“固定距离滑窗”。通过让一个指针先跑,制造出一种“空间换时间”的效果,避免了“先数一遍长度,再跑第二遍”的尴尬。


这个问题紧接着我们刚才讨论的“快慢指针”思路,是该技巧的又一经典应用。

1. 核心技巧:快慢指针(速度差法)

要找到并删除中间节点,我们可以利用“快指针的速度是慢指针的两倍”这一特性:

  • 慢指针 (slow):每次走1步。
  • 快指针 (fast):每次走2步。

当快指针走到链表末尾时,慢指针刚好就在中点附近。

寻找“前驱节点”的艺术

和删除倒数第NNN个节点一样,删除中点必须拿到中点的前一个节点

  • 如果链表长度n=4n = 4n=4,中点是下标222。我们需要停在下标111
  • 如果链表长度n=5n = 5n=5,中点是下标222。我们需要停在下标111

为了方便处理头节点(比如只有一个节点的情况),我们依然请出dummy节点


2. 代码实现 (Python)

classSolution:defdeleteMiddle(self,head:Optional[ListNode])->Optional[ListNode]:# 1. 特判:如果只有一个节点,删除后为空ifnotheadornothead.next:returnNone# 2. 设置虚拟头节点,pre 最终要停在中点的前一个dummy=ListNode(0,head)slow=dummy fast=head# 让 fast 从 head 开始,slow 从 dummy 开始# 3. 快慢指针跑起来# 当 fast 走完 2 步,slow 走 1 步whilefastandfast.next:fast=fast.next.nextslow=slow.next# 4. 此时 slow 恰好停在中点的前驱位置# 比如 1->2->3->4->5,slow 会停在 2,中点是 3slow.next=slow.next.nextreturndummy.next

3. 为什么这样跑能精准定位?

让我们手动模拟一下:

场景 A:长度n=4n = 4n=4(偶数)
链表:dummy -> 1 -> 2 -> 3 -> 4 -> None

  1. 初始:slowdummy,fast1
  2. 第一轮:fast移到3,slow移到1
  3. 第二轮:fast移到None,slow移到2
  4. 循环结束slow停在2。中点下标⌊4/2⌋=2\lfloor 4/2 \rfloor = 24/2=2(即节点3)。slow.next正好指向节点3定位正确!

场景 B:长度n=5n = 5n=5(奇数)
链表:dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> None

  1. 初始:slowdummy,fast1
  2. 第一轮:fast移到3,slow移到1
  3. 第二轮:fast移到5,slow移到2
  4. 此时fast.next为空,循环结束slow停在2。中点下标⌊5/2⌋=2\lfloor 5/2 \rfloor = 25/2=2(即节点3)。slow.next正好指向节点3定位正确!

4. 复杂度分析

  • 时间复杂度O(N/2)O(N/2)O(N/2),即O(N)O(N)O(N)。我们只遍历了半个链表长度(快指针到头即止)。
  • 空间复杂度O(1)O(1)O(1)。只用了两个指针。

总结

链表的题目其实很有规律:

  1. 怕断掉:操作前先存next
  2. 怕头变:先建dummy
  3. 找位置
    • 固定距离用“滑窗双指针”(倒数第NNN个)。
    • 比例距离用“速度差双指针”(中点)。

虽然它们都叫“双指针”,但**“滑窗”“速度差”**在逻辑设计和解决的问题类型上有着本质的区别。

我们可以通过下面这个对比来清晰地理解:


1. 固定距离:滑窗双指针 (Sliding Window)

核心逻辑:两个指针的步速相同(每次都走 1 步),但出发时间不同

  • 操作方法:先让fastNNN步,然后slow才开始从起点走。
  • 物理意义:两指针之间维持一个恒定的刻度线(窗口)
  • 解决的问题:寻找相对于末尾固定位置的节点。
    • 例子:倒数第 5 个。无论链表多长,我只要保证我身后的“影子”跟我差 5 个身位,当我撞到终点墙壁时,影子就在倒数第 5 个。

2. 比例距离:速度差双指针 (Fast & Slow Pointers)

核心逻辑:两个指针的出发时间相同,但步速不同(通常是 2:1)。

  • 操作方法fast每次走 2 步,slow每次走 1 步。
  • 物理意义:两指针之间维持一个恒定的比例关系
  • 解决的问题:寻找相对于全长比例位置的节点。
    • 例子:中点(1/2 处)。因为Vfast=2×VslowV_{fast} = 2 \times V_{slow}Vfast=2×Vslow,所以在相同时间内,slow走过的路程永远是fast的一半。当fast跑完全程(100%)时,slow自然就在中点(50%)。

3. 直观对比表

特性滑窗双指针 (Fixed Gap)速度差双指针 (Ratio Gap)
步频一样 (1:1)不一样 (通常 2:1)
间距固定长度(Gap = N)动态增长(Gap 随步数增加)
关键参数NNN(具体的个数)1/21/21/21/31/31/3(比例)
典型应用倒数第NNN个节点寻找中点、判断是否有环

4. 为什么会有这种区分?

这取决于你对**“终点”**的利用方式:

  • 滑窗利用的是“终点”作为一个锚点。因为我们不知道链表有多长,但我们知道“终点”在哪里,所以通过一个固定长度的“标尺”往回倒推。
  • 速度差利用的是“终点”作为一个计时器。我们用快指针跑完全程的时间,来换取慢指针刚好走到比例位置的结果。

进阶思考:判断“环”

LeetCode 141. 环形链表中,我们使用速度差双指针
为什么不用滑窗?因为在环里,没有“倒数第几个”的概念(没有尽头),但速度差会让快指针在环里“多跑一圈”最终追上慢指针(套圈),就像操场长跑一样。

这就引出了链表最神奇的一个算法:弗洛伊德判圈算法。快慢指针是怎么在环里“相遇”的吗?

这个问题是**“速度差双指针”**最经典的应用场景,通常被称为“龟兔赛跑算法”(Floyd’s Cycle-Finding Algorithm)。


1. 核心原理:套圈

想象一下在一个环形跑道上比赛:

  • 兔子(快指针fast:每次跑 2 个台阶。
  • 乌龟(慢指针slow:每次跑 1 个台阶。

如果跑道是直的,兔子会先到达终点,乌龟永远追不上兔子。
如果跑道有环,兔子进入环后会开始转圈。由于兔子比乌龟快,它最终一定会从后面**“套圈”**乌龟,两人必然会在某个点相遇。


2. 代码实现 (Python)

classSolution:defhasCycle(self,head:Optional[ListNode])->bool:# 如果链表为空或只有一个节点且无环,直接返回 falseifnotheadornothead.next:returnFalseslow=head fast=head# 只要快指针没跑出边界(即没遇到 None)whilefastandfast.next:slow=slow.next# 走 1 步fast=fast.next.next# 走 2 步# 如果两个指针相遇了,说明有环ifslow==fast:returnTrue# 如果跳出了循环,说明快指针走到了终点,没有环returnFalse

3. 深度思考

为什么快指针每次走 2 步?走 3 步、4 步行吗?
  • 2 步是最稳妥的:当快指针进入环并开始追赶慢指针时,它们之间的相对速度是2−1=12 - 1 = 121=1。这意味着它们之间的距离每走一步就缩短 1,最终距离一定会变成 0(相遇),不会跳过去
  • 如果走 3 步:相对速度是 2。如果它们之间的距离是奇数,快指针可能会在某一跨度直接“跳过”慢指针,导致它们需要多跑好几圈才能相遇,甚至在某些离散数学的模型下永远不相遇。
为什么不需要dummy节点?

因为这道题不涉及修改链表结构(不删除也不反转),我们只是在链表上“跑步”。即使从头节点开始就有环,slow == fast的逻辑依然成立,所以不需要哨兵节点。


4. 复杂度分析

  • 时间复杂度O(N)O(N)O(N)
    • 如果没有环,快指针走N/2N/2N/2步到头。
    • 如果有环,快指针进入环后,最多在慢指针转完一圈之前就能追上它。
  • 空间复杂度O(1)O(1)O(1)。我们只用了两个指针,没有使用额外的哈希表。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 4:47:16

女朋友到家前 10 分钟,空调自动开暖风(小智 MCP 实战)

官方文档:https://xiaozhi.dev/docs/development/mcp/故事的开始:她说怕冷 “今天降温好厉害,我一进门就手脚冰凉。” 小禾听完这句话,脑子里只有一个念头:她到家前 10 分钟把空调开到制热,屋里先暖起来。 …

作者头像 李华
网站建设 2026/5/1 4:43:28

离职信怎么写?LobeChat提供体面表达方式

离职信怎么写?LobeChat提供体面表达方式 在职场中,如何得体地告别一份工作,往往比入职更考验情商。一封措辞恰当、结构清晰的离职信,不仅能维护职业形象,还能为未来留下良好口碑。但现实中,很多人面对空白文…

作者头像 李华
网站建设 2026/5/1 0:56:10

linux下RP2350芯片rt-thread开发(四)SRAM性能测试优化

一、前言之前的文章中我仅通过rt-thread系统配置未改动源码的情况下,就在RP2350芯片上跑起了系统和测试。CPU性能测试能完美完成,但用MemoryPerf工具的默认配置去测试SRAM性能还不能精确完成,误差会有些大。本文说明如何优化RP2350芯片的SRAM…

作者头像 李华
网站建设 2026/5/1 3:46:30

LangGraph4j 入门

LangGraph4j 是一个 Java实现的开源 AI 工作流框架,它受到了 Python 版本 LangGraph的启发,能够与 LangChain4j 和 Spring AI无缝集成,而且这个框架还是开源的。 核心特性 1、StateGraph 工作流图 在LangGraph4j 中,StateGraph 是…

作者头像 李华
网站建设 2026/5/1 4:45:41

AI数字人小程序开发实战:基于系统源码的快速落地方案

这两年,AI数字人从概念迅速走向商业化落地。无论是品牌营销、知识付费,还是企业客服、直播带货,越来越多的企业开始意识到:不是要不要做数字人,而是如何用更低成本、更快速度做出一个能用、好用、可扩展的数字人产品。…

作者头像 李华
网站建设 2026/5/1 4:49:21

AI数字人小程序怎么做?从系统源码到产品上线全流程

这两年,“AI数字人”几乎成了企业数字化转型中的高频词。从数字主播、数字客服,到企业IP形象、知识型博主,AI数字人正在被越来越多地应用到实际业务中。而基于小程序的AI数字人产品,因为门槛低、传播快、易变现,也成为…

作者头像 李华