链表操作三板斧:Dummy 节点、快慢指针、头插法,让你从此告别“空指针恐惧症”!

60 阅读4分钟

链表不是玄学,是套路!掌握这三大技巧,轻松搞定删除、反转、找倒数第 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!

高手做法:快慢指针一次遍历搞定!

🧠 思路拆解

  1. 快指针先走 N 步。
  2. 然后快慢指针一起走。
  3. 当快指针走到 最后一个节点(即 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) 空间,要不要我手写给你看?”

记住:链表不是靠猜,是靠套路。而你,已经拿到了秘籍。