← 返回题目列表

链表题里为什么常用虚拟头节点 dummy?

高频 简单 第 2 / 28 题 更新于 2026/07/29
链表虚拟头节点dummy边界处理

简化版

虚拟头节点 dummy 是放在真实头节点前面的辅助节点,用来统一处理“头节点可能被删除、插入、替换”的边界。它不属于最终链表,最后返回 dummy.next,能让代码少写很多 head == null 或“删除第一个节点”的特殊分支。

详细版

链表题最麻烦的是头节点会变。例如删除值为 1 的节点,链表 [1,2,3] 删除后新头变成 2;如果不用 dummy,就要单独处理头节点。加一个 dummy 后,所有删除都变成“删除 prev.next”:

dummy -> 1 -> 2 -> 3
prev = dummy

无论删除的是第一个真实节点还是中间节点,写法都一致。面试里 dummy 常用于删除节点、合并链表、分隔链表、反转局部链表等场景;关键是记住返回 dummy.next,不要把 dummy 当真实数据返回。

完整版教学

一、链表最容易乱在头节点变化

数组删除第一个元素,返回值通常还是同一个数组;链表删除头节点时,head 指针本身会变。

before: head -> 1 -> 2 -> 3
delete 1
after:  head -> 2 -> 3

如果函数内部没有正确更新并返回新头,调用方还拿着旧 head,链表就错了。很多链表 bug 不是指针公式不会写,而是头节点特殊处理漏了。

记忆钩子:dummy 的作用是给真实头节点找一个永远存在的前驱。

二、dummy 让删除头节点和删除中间节点统一

删除节点通常需要知道它的前驱 prev。如果要删除头节点,头节点没有真实前驱,所以要特殊处理。

加 dummy 后:

dummy -> 1 -> 2 -> 3
prev = dummy

删除第一个真实节点和删除中间节点都可以写成:

prev.next = prev.next.next;

这个统一性非常重要。链表题里分支越少,指针断错的概率越低。

三、删除指定值的代码示例

不用 dummy 时,常常要先 while 删除头部连续目标值,再处理后面节点。用 dummy 可以写得更稳定。

ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;

while (prev.next != null) {
  if (prev.next.val == target) {
    prev.next = prev.next.next;
  } else {
    prev = prev.next;
  }
}
return dummy.next;

注意循环看的是 prev.next,因为删除动作发生在 prev.next 上。删除后 prev 不前进,否则可能跳过连续目标节点。

四、合并链表时 dummy 让尾插更自然

合并两个有序链表时,我们需要维护新链表的尾指针。如果没有 dummy,第一次插入要单独决定新头。

ListNode dummy = new ListNode(0);
ListNode tail = dummy;

while (l1 != null && l2 != null) {
  if (l1.val <= l2.val) {
    tail.next = l1;
    l1 = l1.next;
  } else {
    tail.next = l2;
    l2 = l2.next;
  }
  tail = tail.next;
}
tail.next = (l1 != null) ? l1 : l2;
return dummy.next;

dummy 在这里像“结果链表的工作台”。真正结果从 dummy.next 开始。

五、dummy 不是必须,但它降低心智负担

理论上所有 dummy 写法都能改成不用 dummy,只是会多出头节点分支。

场景不用 dummy 的难点用 dummy 的效果
删除节点头节点被删要特殊处理所有节点都有前驱
合并链表第一次插入要定头tail 从 dummy 开始
分隔链表两个结果链表头难维护两个 dummy 分别收集
局部反转子链表前驱可能为空dummy 统一前驱

面试里使用 dummy 不是偷懒,而是主动规避边界复杂度。

六、dummy 的常见细节

dummy 的值通常无意义,可以写 0、-1 或任意值,因为它不会进入返回结果。

dummy.val is ignored
return dummy.next

如果题目有内存释放要求,dummy 只是临时辅助节点。Java、Python 这类语言交给垃圾回收;C/C++ 里如果动态分配 dummy,要注意释放或使用栈上对象。

另一个细节是不要返回 dummy。返回 dummy 会让结果链表多一个不存在的业务节点。

七、常见误区与追问

  • 误区:dummy 是真实链表节点。 dummy 只是辅助节点,最终返回 dummy.next
  • 误区:只有删除头节点才需要 dummy。 合并、分隔、局部反转等需要稳定前驱的场景都适合。
  • 误区:用了 dummy 就不用考虑空链表。 空链表时 dummy.next 为 null,循环条件仍要写对。
  • 追问:删除连续目标值时 prev 为什么不一定前进? 删除后新的 prev.next 可能仍是目标值,前进会跳过检查。
  • 追问:dummy 会不会增加空间复杂度? 只增加 1 个节点,额外空间仍是 O(1)。
  • 追问:什么时候不需要 dummy? 头节点不可能变化、逻辑很简单时可以不用。

八、加强记忆

dummy 可以记成“给头节点补前驱”。链表操作麻烦在头节点可能变化,而 dummy 让第一个真实节点也有前驱,于是删除、合并、分隔、局部反转都能统一成普通节点操作。写完记住两件事:dummy 的值无意义,最终返回 dummy.next