从快慢指针到倒数查找:优雅解决链表倒数问题

从生活场景说起

想象你在一个漫长的队伍中,想知道自己距离队尾还有多少人。一个巧妙的方法是:让你的朋友从你所在位置往后数N步,然后你和朋友一起向后走。当朋友走到队尾时,你的位置就正好是倒数第N个。这个生活中的小技巧,正是我们今天要探讨的链表算法的灵感来源。

问题描述

LeetCode第19题"删除链表的倒数第N个结点"要求:给你一个链表的头节点 head 和一个整数 n ,请你删除链表的倒数第 n 个结点,并且返回链表的头结点。

例如:

输入:1 → 2 → 3 → 4 → 5, n = 2
输出:1 → 2 → 3 → 5
解释:删除倒数第2个节点(值为4) 输入:1 → 2, n = 2
输出:2
解释:删除倒数第2个节点(值为1) 输入:1, n = 1
输出:空链表
解释:删除唯一的节点

初步思路:两次遍历法

最直观的解法是先遍历一遍链表得到长度,然后再遍历一次删除目标节点:

public ListNode removeNthFromEnd(ListNode head, int n) {
// 第一次遍历,计算链表长度
int length = 0;
ListNode current = head;
while (current != null) {
length++;
current = current.next;
} // 如果要删除的是头节点
if (length == n) {
return head.next;
} // 找到待删除节点的前一个节点
current = head;
for (int i = 0; i < length - n - 1; i++) {
current = current.next;
} // 执行删除操作
current.next = current.next.next; return head;
}

优化解法:快慢指针一次遍历

就像我们在队伍中的例子,我们可以用快慢指针在一次遍历内解决问题:

public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建哨兵节点,统一处理头节点的删除
ListNode dummy = new ListNode(0);
dummy.next = head; // 初始化快慢指针
ListNode fast = dummy;
ListNode slow = dummy; // 快指针先走n+1步(多走一步是为了让慢指针停在待删除节点的前一个位置)
for (int i = 0; i <= n; i++) {
fast = fast.next;
} // 快慢指针同步移动
while (fast != null) {
fast = fast.next;
slow = slow.next;
} // 执行删除操作
slow.next = slow.next.next; return dummy.next;
}

图解过程

例子:删除倒数第2个节点
1) 初始状态:
dummy → 1 → 2 → 3 → 4 → 5
S,F 2) 快指针先走n+1步:
dummy → 1 → 2 → 3 → 4 → 5
S F 3) 同步移动直到快指针到末尾:
dummy → 1 → 2 → 3 → 4 → 5
S F 4) 删除slow.next节点:
dummy → 1 → 2 → 3 → 5

深入理解快慢指针解法

为什么这个方法能工作?让我们仔细分析:

  1. 快指针先走n+1步,与慢指针产生n+1的距离
  2. 当快指针到达末尾(null)时,慢指针正好在待删除节点的前一个位置
  3. 这保证了我们总能找到待删除节点的前驱节点,便于执行删除操作

复杂度分析

两次遍历法:

  • 时间复杂度:O(L),需要两次遍历
  • 空间复杂度:O(1)
  • 优点:直观易懂
  • 缺点:需要两次遍历

快慢指针法:

  • 时间复杂度:O(L),只需一次遍历
  • 空间复杂度:O(1)
  • 优点:一次遍历即可完成,更优雅
  • 缺点:需要理解快慢指针的原理

技巧总结

  1. 哨兵节点的使用

    • 统一了头节点的处理
    • 避免了额外的边界检查
  2. 快慢指针的设计

    • 快指针先走n+1步的巧妙设计
    • 同步移动直至快指针到达末尾
  3. 边界情况的处理

    • 链表长度等于n
    • 只有一个节点
    • n等于链表长度

实际应用延伸

这种快慢指针的思想在实际开发中有很多应用:

  1. 缓存淘汰算法
  2. 流式数据的滑动窗口处理
  3. 实时数据处理中的延迟计算

小结与思考

通过这个问题,我们学到了:

  1. 如何用空间换时间(两次遍历)
  2. 如何用巧妙的算法优化空间(快慢指针)
  3. 哨兵节点的实用价值
  4. 如何优雅处理链表的边界情况

当我们遇到类似的"倒数"问题时,可以考虑:

  1. 是否可以用快慢指针解决?
  2. 是否需要哨兵节点简化处理?
  3. 如何在一次遍历中完成任务?

记住:有时看似复杂的问题,用合适的思维方式就能找到优雅的解决方案。


作者:忍者算法

公众号:忍者算法

我准备了一份刷题清单,以及这些题目的详细题解,覆盖了绝大部分常见面试题。我可以很负责任地说,只要你把这些题真正掌握了,80%的算法面试都能遇到相似题目。公众号回复【刷题清单】获取~

【忍者算法】从快慢指针到倒数查找:优雅解决链表倒数问题|LeetCode第19题"删除链表的倒数第N个结点"的更多相关文章

  1. Leetcode(19)-删除链表的倒数第N个节点

    给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. 示例: 给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 ...

  2. LeetCode第[19]题(Java):Remove Nth Node From End of List(删除链表的倒数第N个节点)

    题目:删除链表的倒数第N个节点 难度:Medium 题目内容: Given a linked list, remove the n-th node from the end of list and r ...

  3. 【LeetCode题解】19_删除链表的倒数第N个节点(Remove-Nth-Node-From-End-of-List)

    目录 描述 解法:双指针 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解笔记可以访问我的 github. 描述 给定一个链表,删除链表的倒数第 n 个节点,并且返回 ...

  4. LeetCode 19:删除链表的倒数第N个节点 Remove Nth Node From End of List

    给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. Given a linked list, remove the n-th node from the end of list and ...

  5. 【LeetCode 19】删除链表的倒数第N个节点

    题目链接 [题解] 经典的一道题. 让p1指向链表的第一个元素. 让p2指向链表的第二个元素. 然后让他们俩同时往后移动. 直到p2到达链表的尾巴. 这时p1和p2之间总是隔了n-1个元素. 所以p1 ...

  6. 19。删除链表倒数第N个节点

    class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next# 这道题还是很简单的,我们只 ...

  7. 【LeetCode】19. Remove Nth Node From End of List 删除链表的倒数第 N 个结点

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:链表, 删除节点,双指针,题解,leetcode, 力扣 ...

  8. Leetcode算法系列(链表)之删除链表倒数第N个节点

    Leetcode算法系列(链表)之删除链表倒数第N个节点 难度:中等给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点.示例:给定一个链表: 1->2->3->4-&g ...

  9. 打败算法 —— 删除链表的倒数第n个结点

    本文参考 出自LeetCode上的题库 -- 删除链表的倒数第n个结点,官方的双指针解法没有完全符合"只遍历一遍链表"的要求,本文给出另一种双指针解法 https://leetco ...

  10. 【算法训练营day4】LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表II

    [算法训练营day4]LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表 ...

随机推荐

  1. Mplus数据分析:性别差异gendergap的相关研究如何做?

    再出一篇用mplus做的多组比较和中介分析的文章,专门谈谈诸如性别差异的各种研究的分析方法,从本文中大家不止可以知道性别差异,各种差异,各种gap只要你感兴趣都可以套进来这个方法来进行你的研究设计. ...

  2. PDFSharp 1.5 更新

    PDFsharp 1.50 Preview Information - PDFsharp & MigraDoc PDFShapr 1.50 修复与改进 支持 Object Streams - ...

  3. 2024年1月Java项目开发指南17:自动接口文档配置

    Knife4j 文档 :https://doc.xiaominfo.com/ 有能力的建议自己去看文档配置,本文仅做参考,因为官方文档会更新,本文不会,以后说不定本文就过时了. ok,我们继续.虽然本 ...

  4. 在Ubuntu系统上手动安装GCC环境

    Ubuntu系统是自带GCC安装指令的apt install gcc,当前apt源中gcc版本为5.4.0,版本太低,推荐手动安装gcc8.3.0 手动安装gcc8.3.0之前需要先确保安装gcc环境 ...

  5. IDEA和GIT关于文件中LF和CRLF问题

    问题描述:项目软件安装shell脚本上git仓库管理,但拉取后,上linux运行报错. 问题思考:根据描述信息可以查看到\r字样,初步判别为换行符导致 1.将脚本文件移动至notepad++中,通过视 ...

  6. Xshell无法连接22端口问题解决办法汇总

    Xshell软件在进行远程连接过程中,会出现端口连接报错的问题,提示:"该IP地址的22端口连接失败",这是怎么回事?今天小编就xshell软件无法连接22端口的问题,整理相关情形 ...

  7. MybatisPlusException: can not find lambda cache for this entity[]异常解决

    文章目录 场景说明 解决方案 场景说明   简单来说,我们系统中许多数据都是树状结构的,所以我定义了一个实体类父类BaseTreePO,并且想封装一个通用的树状对象的Service类,部分代码如下: ...

  8. Qt开发经验小技巧111-120

    在不同的平台上文件路径的斜杠也是不一样的,比如linux系统一般都是 / 斜杠,而在windows上都是 \ 两个反斜杠,Qt本身程序内部无论在win还是linux都支持 / 斜杠的路径,但是一些第三 ...

  9. 陌陌技术分享:陌陌IM在后端KV缓存架构上的技术实践

    本文由冀浩东分享,原题"单核QPS近6000S,陌陌基于OceanBase的持久化缓存探索与实践",为了阅读便利,本文进行了排版和内容优化等. 1.引言 挚文集团于 2011 年 ...

  10. 记录一个uniapp写的小程序的手写板,横屏,用于签名,也可竖屏

    今天需要在小程序增加一个手写板的功能,但是得横向的手写纵向的保存,直接上代码,竖屏的时候不需要旋转图片 <template> <view class="wrapper&qu ...