链表不是玄学,是套路!掌握这三大技巧,轻松搞定删除、反转、找倒数第 N 个节点等高频面试题。
🧠 前言:为什么你一写链表就“段错误”?
有没有这样的经历:
- 写个删除节点,结果删着删着把整个链表弄丢了?
- 反转链表时,脑子比指针还乱,最后
head指向了火星? - 找倒数第 N 个节点?先遍历一遍算长度?太慢了,面试官都打哈欠了……
别慌!今天我们就来揭开链表操作的“神秘面纱”,用 Dummy 节点、快慢指针、头插法 这三板斧,把链表玩得明明白白!
🛡️ 第一板斧:Dummy 节点 —— “万能前驱”,专治各种边界
❓ 问题来了:为什么要 Dummy?
想象一下,你要删除链表中值为 x 的节点。如果这个节点正好是 头节点,那你得单独处理——因为头节点没有前驱!
于是代码变成了:
if (head && head.val === x) {
return head.next;
}
// ...再处理其他情况
丑!而且容易漏!更可怕的是,如果你要删多个、或者在循环里删,逻辑会越来越复杂。
✅ 解法:给链表加个“假头”!
我们人为地在链表最前面加一个 不存真实数据 的节点,叫 Dummy 节点(哨兵节点) 。它唯一的任务就是:当你的前驱。
const dummy = new ListNode(0);
dummy.next = head;
现在,所有节点都有前驱了!包括原来的头节点。
💡 删除任意值节点(通用写法)
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; // 返回真正的头
}
🎯 关键点:
cur始终指向 待删除节点的前一个,所以不会丢失引用!
🔁 第二板斧:头插法 + Dummy = 链表反转神器
链表反转是面试高频题,很多人第一反应是递归,但其实 迭代 + 头插法 更直观、更高效!
🤔 什么是头插法?
每次从原链表“拔”出一个节点,插到新链表的最前面。就像排队插队一样——但你是 VIP,永远站第一位!
🧩 Dummy 在这里干啥?
它充当 新链表的虚拟头,它的 next 始终指向当前已反转部分的 真实头节点。
💻 反转代码(带注释版)
function reverseList(head) {
const dummy = new ListNode(0); // 假头,不存数据
let cur = head;
while (cur) {
const next = cur.next; // 1️⃣ 先保存下一个,不然就丢了!
cur.next = dummy.next; // 2️⃣ 当前节点指向已反转的头
dummy.next = cur; // 3️⃣ 当前节点成为新的头
cur = next; // 移动到下一个
}
return dummy.next; // 返回反转后的真实头
}
✨ 记忆口诀:保、插、升
保(保存 next)→ 插(插入到 dummy 后)→ 升(cur 升级为 next)
🏃♂️ 第三板斧:快慢指针 —— 链表界的“GPS 定位系统”
快慢指针是链表中的经典双指针技巧,用途极广:
- 判断是否有环
- 找中间节点
- 找倒数第 N 个节点(重点!)
🎯 场景:删除倒数第 N 个节点
暴力解法?先遍历一次求长度 L,再走 L - N 步?Too slow!
高手做法:快慢指针一次遍历搞定!
🧠 思路拆解
- 快指针先走 N 步。
- 然后快慢指针一起走。
- 当快指针走到 最后一个节点(即
fast.next === null) 时,慢指针正好停在 倒数第 N 个节点的前一个!
为什么?因为快慢之间始终隔着 N 个节点!
🛠️ 加上 Dummy 节点,稳如老狗
即使要删的是头节点(比如 N = 链表长度),Dummy 也能兜底!
function removeNthFromEnd(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;
}
// 一起走,直到 fast 到最后一个节点
while (fast.next) {
fast = fast.next;
slow = slow.next;
}
// slow 现在是倒数第 N 个的前一个
slow.next = slow.next.next;
return dummy.next;
}
✅ 时间复杂度:O(L),空间 O(1),一次遍历,优雅至极!
🔄 Bonus:快慢指针判断环(Floyd 算法)
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true; // 相遇 = 有环
}
return false;
}
🐢 兔子(fast)和乌龟(slow)赛跑,如果跑道是环形的,兔子迟早会追上乌龟!
🧩 总结:链表三板斧速查表
| 技巧 | 用途 | 关键点 |
|---|---|---|
| Dummy 节点 | 统一边界处理 | 所有节点都有前驱,不怕删头 |
| 头插法 + Dummy | 链表反转 | 保 → 插 → 升 |
| 快慢指针 | 找倒数第 N 个、判环、找中点 | 快先走 N 步,同步走,定位精准 |
💬 最后说两句
链表看似简单,实则暗藏玄机。但只要你掌握了 Dummy 节点 这个“万能前驱”,配合 快慢指针 和 头插法,就能像庖丁解牛一样游刃有余。
下次面试官再问:“怎么删倒数第 N 个节点?”
你可以微微一笑:“我用快慢指针,一次遍历,O(1) 空间,要不要我手写给你看?”
记住:链表不是靠猜,是靠套路。而你,已经拿到了秘籍。