行业门户网站案例分析,刷QQ砖的网站咋做,网站开发 一个页面多少钱,校园文化建设图片网站引言 力扣第 19 题#xff1a;给你一个链表#xff0c;删除链表的倒数第 n 个结点#xff0c;并且返回链表的头结点。 这道题看似简单#xff0c;却像一颗洋葱——剥开一层#xff0c;还有一层。它背后隐藏着链表操作中三大核心技巧的精妙融合#xff1a;dummy 哨兵节点、…引言力扣第 19 题给你一个链表删除链表的倒数第n个结点并且返回链表的头结点。这道题看似简单却像一颗洋葱——剥开一层还有一层。它背后隐藏着链表操作中三大核心技巧的精妙融合dummy 哨兵节点、链表反转思想、快慢指针策略。本文将带你从最基础的链表删除出发层层递进最终构建出高效、优雅、鲁棒的解决方案。题目链接19. 删除链表的倒数第 N 个结点 - 力扣LeetCode第一章初识链表删除 —— 边界之痛与 dummy 节点的诞生1.1 最朴素的删除实现无 dummy假设我们要删除链表中值为val的第一个节点。最容易想到的写法如下function remove(head, val) { // 要删除的节点是头节点 对头节点特殊处理 // 能不能省头节点没有前驱节点尾节点后没有后继节点 // 给它一个前驱节点 dummy 节点假的 哨兵节点 // 遍历 if (head head.val val) { return head.next } let cur head while(cur.next) { // 节点的遍历 if (cur.next.val val) { cur.next cur.next.next break; } cur cur.next } return head }这段代码逻辑清晰但有一个致命弱点必须对头节点做特殊判断。为什么因为在链表中只有头节点没有前驱节点。当我们想删除某个节点时通常需要修改其前一个节点的next指针。但对于头节点没有“前一个”只能直接返回head.next。这就导致代码分支增多可读性下降容易遗漏边界情况比如空链表、只有一个节点等无法统一处理所有删除场景1.2 引入 dummy 节点统一删除逻辑为了解决这个问题我们引入一个人为添加的假节点——dummy 节点哨兵节点。function remove (head,val){ const dummy new ListNode(0); dummy.next head; let cur dummy; while(cur.next) { if (cur.next.val val) { cur.next cur.next.next break; } cur cur.next } return dummy.next }dummy 节点的核心作用它不存储真实数据值为 0 或任意占位符它的next指向原链表的头节点它为原头节点提供了一个“虚拟前驱”这样一来所有节点包括原头节点都有了前驱删除逻辑完全统一无需任何特殊判断。✅关键洞见dummy 节点不是为了“解决问题”而是为了“消除问题”——通过结构改造让边界情况自然融入通用逻辑。第二章dummy 节点的高阶应用 —— 链表反转中的动态头指针dummy 节点不仅用于删除还能在构建新链表结构时大显身手。以链表反转为例// 使用dummy哨兵节点 头插法 function reverseList(head) { // 他的next将始终指向当前已反转部分的头节点 const dummy new ListNode(0); let cur head; while(cur) { // 先保存下一个节点 const next cur.next; // 头插法 // 先将当前节点的next指向已反转部分的头节点 // 再将dummy的next指向当前节点完成头插 cur.next dummy.next; // 头插完成后更新dummy的next指向当前节点 dummy.next cur; // 头插完成后更新当前节点为下一个节点 cur next; } // 最后返回 dummy 的 next 节点即新的头节点 return dummy.next; }这里dummy 节点扮演了动态头指针的角色初始时dummy.next null每次将当前节点插入到dummy之后即“头插”dummy.next始终指向当前已反转部分的头节点这个过程可以理解为原链表1 → 2 → 3 → null 步骤1: 插入1 → dummy → 1 → null 步骤2: 插入2 → dummy → 2 → 1 → null 步骤3: 插入3 → dummy → 3 → 2 → 1 → null最终返回dummy.next即为新头节点3。✅关键洞见dummy 节点不仅是“保护盾”还可以是“指挥中心”——它的next指针可以动态维护某种结构状态如反转链表的头。虽然反转与删除看似无关但它们共享同一哲学通过引入辅助结构简化主逻辑。第三章定位难题 —— 如何在一次遍历中找到倒数第 N 个节点现在回到力扣第 19 题删除倒数第 N 个节点。3.1 朴素解法两次遍历最直接的想法第一次遍历计算链表长度L第二次遍历走到第L - N个节点即目标节点的前一个执行删除但题目隐含要求最好只遍历一次虽然未明说但这是考察快慢指针的关键。3.2 快慢指针一次遍历的魔法快慢指针Two Pointers是解决“距离定位”类问题的经典技巧。核心思想让两个指针fast和slow同时从起点出发fast先走N步然后fast和slow一起每次走一步当fast到达链表末尾null时slow恰好位于倒数第 N 个节点的前一个为什么因为两者始终保持N步的距离图解示例链表[1, 2, 3, 4, 5]n 2删除 4初始 dummy → 1 → 2 → 3 → 4 → 5 → null ↑ fast, slow fast 先走 2 步 dummy → 1 → 2 → 3 → 4 → 5 → null ↑ ↑ slow fast 然后一起走直到 fast.next null dummy → 1 → 2 → 3 → 4 → 5 → null ↑ ↑ slow fast 此时 slow 指向 3即倒数第 2 个节点4的前一个 执行 slow.next slow.next.next → 删除 43.3 为什么必须用 dummy 节点考虑极端情况删除头节点即倒数第 L 个节点L 为长度例如[1, 2],n 2→ 应删除1返回[2]如果没有 dummyfast从head出发走 2 步 →fast nullslow还在head但我们需要的是“头节点的前一个”来执行删除而它不存在有了 dummyfast和slow从dummy出发fast走 2 步 → 指向2然后fast.next为null循环结束slow停在dummyslow.next就是头节点1执行slow.next slow.next.next→ 完美删除头节点关键洞见快慢指针负责定位dummy 节点负责安全删除——二者缺一不可。第四章终极融合 —— 力扣第 19 题的完整实现现在我们将前面所有知识融会贯通写出最终解法// 删除链表的倒数第 N 个节点 const removeNthFromEnd function(head, n) { // dummy 节点 const dummy new ListNode(0); dummy.next head; let fast dummy; let slow dummy; // 快指针先移动 N 步 for (let i 0; i n; i) { fast fast.next; } // 慢指针开始移动 while(fast.next) { // 末尾 fast fast.next; slow slow.next; } // 慢指针指向的就是倒数第 N 个结点的前一个 slow.next slow.next.next; // dummy确保了 有值 return dummy.next; }逐行解析创建 dummy 节点const dummy new ListNode(0); dummy.next head;为整个链表添加虚拟头节点统一后续操作双指针初始化let fast dummy; let slow dummy;两者都从 dummy 开始确保能处理删除头节点的情况快指针先行 N 步for (let i 0; i n; i) { fast fast.next; }建立 fast 与 slow 之间的 N 步距离双指针同步移动while(fast.next) { fast fast.next; slow slow.next; }条件是fast.next而非fast因为我们要让fast停在最后一个真实节点上此时slow恰好停在目标节点的前一个执行删除slow.next slow.next.next;标准链表删除操作返回结果return dummy.next;dummy 的 next 始终指向真正的头节点即使原头被删第五章边界情况全面验证让我们验证几个关键测试用例情况1删除中间节点输入[1,2,3,4,5],n2输出[1,2,3,5]✅ 正常工作情况2删除头节点输入[1,2],n2输出[2]✅ dummy 确保 slow 能定位到虚拟前驱情况3删除唯一节点输入[1],n1输出[]即nullfast 先走 1 步 → 指向1fast.next为null不进入 while 循环slow 仍在 dummyslow.next slow.next.next→dummy.next null返回null✅ 正确情况4n 等于链表长度同情况2已覆盖✅ 安全第六章与其他链表技巧的横向对比6.1 与“判断链表是否有环”对比判断环也用快慢指针function hasCycle(head) { let slow head; let fast head; while(fast fast.next) { fast fast.next.next; slow slow.next; if (fast slow) { return true; } } return false; }区别目的不同一个是检测相遇环一个是保持固定距离定位起始点不同环检测从head开始删除从dummy开始终止条件不同环检测看是否相遇删除看fast.next是否为空但共通点是利用速度差实现空间或位置关系的捕捉。6.2 与“链表反转”对比反转用 dummy 作为动态头删除用 dummy 作为安全前驱。相同点都借助 dummy 简化逻辑不同点反转是构建删除是破坏第七章总结 —— 三大技巧的协同哲学技巧作用在本题中的体现dummy 节点消除边界统一操作为头节点提供前驱确保删除安全快慢指针一次遍历定位距离精准找到倒数第 N 个节点的前一个多指针协作分工明确逻辑清晰fast 负责探路slow 负责执行这道题之所以经典正是因为它不是单一技巧的展示而是多个基础思想的有机融合。编程的最高境界不是记住多少算法而是懂得如何组合基本构件解决复杂问题。附录完整代码// 删除链表的倒数第 N 个节点 const removeNthFromEnd function(head, n) { // dummy 节点 const dummy new ListNode(0); dummy.next head; let fast dummy; let slow dummy; // 快指针先移动 N 步 for (let i 0; i n; i) { fast fast.next; } // 慢指针开始移动 while(fast.next) { // 末尾 fast fast.next; slow slow.next; } // 慢指针指向的就是倒数第 N 个结点的前一个 slow.next slow.next.next; // dummy确保了 有值 return dummy.next; }结语从一行删除代码的烦恼到 dummy 节点的灵光一现从链表反转的头插法到快慢指针的巧妙定位最终我们在力扣第 19 题中见证了这些思想的完美交汇。下次当你面对链表问题时不妨自问我是否需要一个 dummy 来消除边界能否用快慢指针一次搞定多个指针如何分工协作答案或许就藏在这篇长文中。