2026/4/16 2:06:33
网站建设
项目流程
企业网络推广做网站推广公司,珠海电商网站制作,厦门建设工程信息网官网,ui培训有用么哨兵节点与快慢指针#xff1a;轻松破解链表算法题#x1f517;
在链表相关的算法题中#xff0c;边界条件处理往往是最让人头疼的 —— 比如删除头节点、链表为空、环的判断等。今天就来聊聊两个 “神器”#xff1a;哨兵节点#xff08;dummy node#xff09; 和快慢指…哨兵节点与快慢指针轻松破解链表算法题在链表相关的算法题中边界条件处理往往是最让人头疼的 —— 比如删除头节点、链表为空、环的判断等。今天就来聊聊两个 “神器”哨兵节点dummy node和快慢指针它们能帮我们优雅地解决这些问题让链表操作变得简单起来一、删除链表节点传统做法的 “小麻烦”先从最基础的 “删除链表中值为 val 的节点” 说起。传统做法通常需要分两种情况处理若要删除的是头节点直接返回head.next即可若要删除的是中间节点需要遍历链表找到目标节点的前驱再修改指针跳过目标节点。来看一段传统做法的代码javascriptfunction remove(head, val) { // 单独处理头节点 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; }代码解释先判断头节点是否为目标节点若是则直接返回下一个节点用cur指针遍历链表通过cur.next判断是否为目标节点找到后修改指针完成删除。缺点需要单独处理头节点的情况逻辑不够统一。如果链表为空head是null还可能出现空指针异常。这时候“哨兵节点” 就要登场了二、哨兵节点dummy简化边界的 “万能钥匙”哨兵节点是一个不存储真实数据的假节点通常放在链表的最前面偶尔也在尾部。它的核心作用是消除头节点的特殊性让所有节点的操作逻辑保持一致。用哨兵节点优化删除操作来看优化后的代码javascriptfunction 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; // 哨兵节点的next就是新的头节点 }代码解释创建dummy哨兵节点让它的next指向原链表的头节点此时原头节点和其他节点一样都有了前驱dummy用cur从dummy开始遍历通过cur.next判断目标节点找到后直接修改cur.next即可完成删除无论目标节点是头节点还是中间节点逻辑完全一致最后返回dummy.next避免了头节点被删除后返回null的处理难题。有了哨兵节点再也不用为 “头节点是否存在”“是否删除头节点” 这些问题纠结了是不是很方便三、LeetCode 206. 反转链表哨兵节点 头插法反转链表是经典问题用 “哨兵节点 头插法” 可以高效解决。头插法的核心是把原链表的节点依次 “摘” 下来插入到新链表的头部而哨兵节点可以作为新链表的 “锚点”。来看代码javascriptfunction reverseList(head) { const dummy new ListNode(0); // 哨兵节点其next始终指向已反转部分的头 let cur head; // 当前要处理的节点 while(cur) { const next cur.next; // 1. 保存下一个节点避免丢失 cur.next dummy.next; // 2. 让当前节点指向已反转的头 dummy.next cur; // 3. 当前节点成为新的反转头 cur next; // 处理下一个节点 } return dummy.next; // 反转后的新头节点 }代码解释dummy哨兵节点初始时next为null因为刚开始反转部分为空遍历原链表的每个节点cur分三步操作先用next保存cur的下一个节点否则后续步骤会覆盖cur.next导致原链表丢失让cur的next指向dummy.next即已反转部分的头节点第一次循环时指向null让dummy.next指向cur此时cur成为新的反转头循环结束后dummy.next就是反转后链表的头节点。通过哨兵节点我们不用单独处理 “第一个节点反转后指向 null” 的边界情况逻辑清晰又简洁四、快慢指针解决链表 “距离” 与 “环” 的利器快慢指针是另一种处理链表问题的经典技巧两个指针往同一个方向移动快指针速度比慢指针快通常快 1 倍。它能解决诸如 “环的判断”“倒数第 N 个节点” 等涉及 “距离” 或 “循环” 的问题。LeetCode 141. 环形链表用快慢指针找环判断链表是否有环用快慢指针再合适不过了 —— 就像跑步时快的人总会追上慢的人如果赛道是环形的话。来看代码javascriptfunction hasCycle(head) { let slow head; let fast head; while(fast fast.next) { // 快指针先到尾部无环 slow slow.next; // 慢指针走1步 fast fast.next.next; // 快指针走2步 if(slow fast) { // 相遇则有环 return true; } } return false; // 快指针到尾部无环 }代码解释初始时slow和fast都指向头节点循环中slow每次走 1 步fast每次走 2 步若链表无环fast会先到达末尾fast或fast.next为null循环结束返回false若链表有环fast会在环内循环最终追上slow因为速度快此时slow fast返回true。这个逻辑就像 “套圈”—— 只要赛道是环形快的人一定能追上慢的人是不是很形象五、哨兵节点 快慢指针综合运用的威力LeetCode 141. 环形链表用快慢指针找环有些问题需要两种技巧结合比如 “删除链表的倒数第 N 个节点”LeetCode LCR 021。既要找到倒数第 N 个节点又要处理删除头节点的边界情况这时候 “哨兵 快慢” 就是绝配来看代码javascriptconst removeNthFromEnd function (head, n) { 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; return dummy.next; }代码解释哨兵节点dummy的作用避免删除头节点时的特殊处理比如链表只有 1 个节点删除后返回null同时确保slow.next.next一定有效不会出现空指针。快慢指针的配合快指针先移动 N 步此时快慢指针之间相差 N 个节点两者同时移动当快指针到达链表末尾fast.next为null时慢指针恰好指向 “倒数第 N 个节点的前驱”直接修改slow.next即可删除目标节点。通过两者结合既高效找到目标节点只需遍历一次链表又完美处理了边界情况简直是 “112” 的效果六、面试官可能会问这些问题哨兵节点的核心作用是什么答消除头节点的特殊性让所有节点的操作逻辑统一简化边界条件如空链表、删除头节点等。反转链表时为什么要用头插法 哨兵节点答头插法能高效反转时间复杂度 O (n)哨兵节点作为 “锚点”避免处理 “第一个节点反转后指向 null” 的特殊逻辑。3.环形链表中快慢指针为什么一定会相遇答若有环快指针速度是慢指针的 2 倍相对速度为 1 步 / 次最终会追上慢指针类似套圈。删除倒数第 N 个节点时快慢指针的距离为什么是 N答快指针先移 N 步两者同步移动后快指针到末尾时慢指针刚好在目标节点的前驱距离末尾 N 个节点。七、结语链表问题的难点往往在于边界条件而哨兵节点和快慢指针就像两把 “钥匙”能帮我们打开这些难题的大门哨兵节点让 “头节点” 不再特殊统一操作逻辑快慢指针轻松解决 “环” 和 “距离” 相关问题降低时间复杂度。掌握这两个技巧后再遇到链表题时不妨先想想“能不能用哨兵节点简化边界”“快慢指针能帮我找到目标位置吗” 多加练习就能让链表操作变得像 “搭积木” 一样简单啦