🤯你以为链表很简单?——这是面试官最爱下刀的地方

49 阅读4分钟

在算法面试里,有一种数据结构,代码不长,但坑极多
写的时候感觉没问题,一运行就 nullundefined、指针飞了;
面试官一追问: “为什么要用 dummy 节点?”
你就开始怀疑人生。

没错,它就是——链表(Linked List)

这篇文章,我会完全基于你提供的那些“高频必考素材”,
从面试视角出发,带你系统吃透链表算法:

  • dummy 哨兵节点为什么是“面试外挂”
  • 删除节点到底在删谁?
  • 链表反转为什么是指针操作的分水岭
  • 快慢指针是怎么“凭空定位”倒数第 N 个节点的
  • 如何在面试中,把思路讲清楚,而不是只会背模板

目标只有一个:

👉 让你在面试中,写得出、讲得清、还显得很专业。


一、为什么链表总是面试“重灾区”?

数组 vs 链表,本质差别只有一句话:

数组靠下标,链表靠指针。

但正是这个“靠指针”,让链表成为面试官的快乐源泉:

  • 没有前驱 / 后继怎么办?
  • 删除头节点是不是要特判?
  • 指针一断,后面的节点去哪了?

所以面试官问链表,从来不是想看你 API 用得熟不熟,
而是想看你:

👉 有没有指针思维

而这一切的核心钥匙,就是下面这个东西。


二、dummy 节点:链表算法里的“外挂装备”

1️⃣ dummy 节点是什么?

dummy 节点(也叫哨兵节点):

  • 人为创建的假节点
  • 不存储真实数据
  • 通常放在链表最前面
const dummy = new ListNode(0)
dummy.next = head

它的存在只有一个目的:

抹平边界条件。

2️⃣ 为什么面试官都爱 dummy?

我们来想一个经典问题:

删除链表中值为 val 的节点

如果没有 dummy,你一定写过这种代码:

if (head && head.val === val) {
  return head.next
}

这就是问题:

  • 头节点 没有前驱
  • 必须写特判
  • 逻辑被拆成两套

而用了 dummy 之后:

let cur = dummy
while (cur.next) {
  if (cur.next.val === val) {
    cur.next = cur.next.next
    break
  }
  cur = cur.next
}

🎯 统一逻辑、没有特判、思路清晰。

面试官看到 dummy,潜台词只有一句:

👍 这个人写过不少链表题。


三、删除链表节点:你真正删的是谁?

面试官常问:

“删除一个节点,你删的是当前节点吗?”

答案是:

不是,你删的是前驱的 next。

链表里没有“删除自己”这种操作,只有:

前一个节点.next = 被删节点.next

经典删除模板(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 节点,让每个节点都有前驱,
删除操作本质就是修改前驱节点的 next 指针。”

这是加分回答。


四、链表反转:指针操作的分水岭

如果说删除节点是入门题,
链表反转就是:

🚪 面试是否放你进下一轮的门槛。

反转为什么这么重要?

因为它考察的是:

  • 指针修改顺序
  • 是否会“断链”
  • 对当前 / 下一个节点的掌控

核心思想:头插法 + dummy

function reverseList(head) {
  const dummy = new ListNode(0)
  let cur = head

  while (cur) {
    const next = cur.next     // ① 先保存下一个节点
    cur.next = dummy.next     // ② 指向已反转部分的头
    dummy.next = cur          // ③ 成为新的反转头
    cur = next                // ④ 继续遍历原链表
  }
  return dummy.next
}

面试官最爱追问的一句:

“为什么一定要先保存 next?”

标准回答:

👉 因为一旦修改 cur.next
原链表的后半部分就可能丢失。

先保存,再断链,是所有指针题的铁律。


五、快慢指针:链表里的“时间魔法”

快慢指针的本质是:

通过速度差,制造信息差。

1️⃣ 判断链表是否有环

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
}

解释给面试官听:

“如果链表有环,快指针一定会在环内追上慢指针;
如果没有环,快指针会先到 null。”

这道题考的是:

  • 指针同步移动
  • 循环退出条件

六、删除倒数第 N 个节点:面试高频王中王

面试官表面在问:

删除倒数第 N 个节点

实际在考:

  • dummy 节点
  • 快慢指针
  • 指针距离控制

核心思路一句话版:

让快指针先走 N 步,制造一个“固定距离”。

const removeNthFromEnd = function(head, n) {
  const dummy = new ListNode(0)
  dummy.next = head

  let fast = dummy
  let slow = dummy

  for (let i = 0; i < n; i++) {
    fast = fast.next
  }

  while (fast.next) {
    fast = fast.next
    slow = slow.next
  }

  slow.next = slow.next.next
  return dummy.next
}

为什么一定要 dummy?

因为:

  • 可能删除的是头节点
  • slow 必须停在 目标节点的前一个

dummy 再一次拯救了边界条件。


七、面试总结:链表题的“通关口诀”

最后,送你一套真正有用的链表面试 checklist

✅ 只要涉及删除 / 反转 → 先想 dummy

✅ 改指针前 → 先保存 next

✅ 倒数 / 环 / 中点 → 快慢指针

✅ 永远想清楚:

👉 这一轮指针,和上一轮的关系是什么?

如果你能在面试中,把这些逻辑讲清楚,
链表题就不再是扣分项,而是:

🌟 你的加分表演时间。


如果你正在刷 LeetCode Hot100,
这套思维可以覆盖 80% 的链表题型