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

19.删除链表的倒数第N个结点

题目描述

前言

在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了

题解一(计算链表长度)(最容易想到)

思路

  • 从头节点开始对链表进行一次遍历,得到链表的长度 L。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 L−n+1 个节点时,它就是我们需要删除的节点
  • 为了方便删除操作,我们可以从哑节点开始遍历 L−n+1 个节点。当遍历到第 L−n+1 个节点时,它的下一个节点就是我们需要删除的节点

代码

classSolution{publicListNoderemoveNthFromEnd(ListNodehead,intn){ListNodedummy=newListNode(0,head);intlength=getLength(head);ListNodecur=dummy;for(inti=1;i<length-n+1;++i){cur=cur.next;}cur.next=cur.next.next;ListNodeans=dummy.next;returnans;}publicintgetLength(ListNodehead){intlength=0;while(head!=null){++length;head=head.next;}returnlength;}}

复杂度分析

  • 时间复杂度:O(L),其中 L 是链表的长度
  • 空间复杂度:O(1)

题解二(双指针)(最优雅最高效)

思路

  • 如果我们要找到倒数第nnn个节点,可以让一个“快指针”先走nnn步,然后“快指针”和“慢指针”再一起同步移动。当“快指针”走到链表末尾(nullnullnull)时,“慢指针”刚好就停在倒数第nnn个节点上

具体步骤

代码

classSolution{publicListNoderemoveNthFromEnd(ListNodehead,intn){// 1. 创建虚拟头节点,统一操作逻辑(处理删除真正头节点的情况)ListNodedummy=newListNode(0,head);// 2. 初始化快慢指针,都指向虚拟头节点ListNodefast=dummy;ListNodeslow=dummy;// 3. 让 fast 指针先走 n + 1 步for(inti=0;i<=n;i++){fast=fast.next;}// 4. fast 和 slow 同时走,直到 fast 到达链表末尾的 nullwhile(fast!=null){fast=fast.next;slow=slow.next;}// 5. 此时 slow 停在倒数第 n 个节点的前一个节点,执行删除操作slow.next=slow.next.next;// 6. 返回新的头节点(注意不要返回 head,因为 head 有可能被删除了)returndummy.next;}}

复杂度分析

  • 时间复杂度:O(L)\mathcal{O}(L)O(L),其中LLL是链表的长度。fast 指针和 slow 指针最多只遍历链表一次,因此实现了一趟遍历的解法
  • 空间复杂度:O(1)\mathcal{O}(1)O(1)。只使用了常数个额外的指针变量 fast、slow 和 dummy,没有开辟额外的线性空间

题解三(栈)

思路

  • 在遍历链表的同时将所有节点依次入栈。
    根据栈「先进后出」的原则,我们弹出栈的第 n 个节点就是需要删除的节点
    并且目前栈顶的节点就是待删除节点的前驱节点

代码

classSolution{publicListNoderemoveNthFromEnd(ListNodehead,intn){ListNodedummy=newListNode(0,head);Deque<ListNode>stack=newLinkedList<ListNode>();ListNodecur=dummy;//这里要把dummy节点也入栈,具体原因请看下文分析while(cur!=null){stack.push(cur);cur=cur.next;}for(inti=0;i<n;++i){stack.pop();}ListNodeprev=stack.peek();prev.next=prev.next.next;ListNodeans=dummy.next;returnans;}}

为什么dummy节点要入栈?

大部分情况下dummy不入栈没问题,
但是当你要删除的刚好是链表的第一个节点时
(比如链表只有 [1] 且 n=1,或者链表是 [1, 2] 且 n=2),就会崩溃

复杂度分析

  • 时间复杂度:O(L),其中 L 是链表的长度
  • 空间复杂度:O(L),其中 L 是链表的长度。主要为栈的开销
http://www.jsqmd.com/news/586009/

相关文章:

  • YouTube 系统设计思维研究
  • 3大方案解决Notepad--的内存性能问题:从卡顿到流畅的全方位优化指南
  • 高效GPU显存健康检测:memtest_vulkan全面解析与实战指南
  • 实战应用:基于快马ai项目复现企业级vmware测试环境搭建全过程
  • 从0到1部署MatAnyone:视频智能抠像工具的5个实用步骤
  • S2-Pro多轮对话与上下文管理实战:构建有记忆的聊天机器人
  • 遥感图像处理神器Git-RSCLIP:上传图片输入标签,结果立现
  • ai辅助开发:让快马平台智能解析vm16许可证密钥的奥秘
  • Jenkins 自动化部署:从代码提交到上线一条龙
  • 实战指南:基于快马平台与yolov5构建安全帽检测系统原型
  • 专业Windows系统优化指南:如何用免费工具5分钟解决C盘空间不足问题
  • Wan2.1-umt5一键部署实战:Python环境快速配置与模型调用
  • 3个关键步骤:用Ryujinx模拟器在PC上体验Switch游戏的完整指南
  • EdgeRemover:Windows系统深度集成浏览器的智能管理方案
  • WindowsCleaner:让你的电脑重获新生的系统清理工具
  • LeetCode 两数之和 思路 + 题解
  • 如何高效使用openLCA:环境评估的完整实战指南
  • 快速原型:用快马AI十分钟构建智能应用控制解除工具Demo
  • Docker部署AnythingLLM踩坑记:解决SQLite数据库文件无法打开的权限问题
  • Clark 变换与反 Clark 变换
  • 实战即战力:基于快马为狼蛛f87pro快速生成游戏与专业软件键位方案
  • 保姆级教程:在Ubuntu 20.04上用Python+Bluez 5.50实现你的第一个BLE广播设备
  • 5倍提效:Picasso设计稿转代码全流程实战指南
  • 如何让Windows播放器支持所有视频格式:终极媒体解码解决方案
  • Spring循环依赖深度解析:从三级缓存原理到跨环境“灵异”现象
  • 银泰百货卡回收心得分享:如何避免回收陷阱? - 团团收购物卡回收
  • 09-ESP32-IDF日志系统实战:从配置到高级调试技巧
  • 黑丝空姐-造相Z-Turbo多模型对比:与Claude Code在创意编程上的协同
  • 从底层源码深入分析Bean的实例化
  • 快速原型设计:借助快马ai十分钟搭建python编程练习题验证系统