← 返回题目列表

删除链表倒数第 N 个节点为什么用快慢指针?虚拟头节点有什么作用?

中等 第 22 / 27 题 更新于 2026/07/31
双指针快慢指针链表

简化版

删除倒数第 N 个节点可以用快慢指针一趟完成。

先让快指针领先慢指针 n 步,然后两个指针一起走;当快指针到末尾时,慢指针就在待删除节点的前一个位置。

为了统一删除头节点的情况,通常加一个虚拟头节点 dummy

详细版

这题的核心是把“倒数第 N 个”转成“两个指针之间保持 N 个节点间隔”。

创建 dummy 指向 head,让 fastslow 都从 dummy 出发。先让 fastn 步,再让 fastslow 同步前进,直到 fast.next === null

此时 slow.next 就是要删除的节点,执行:

slow.next = slow.next.next

最后返回 dummy.next

这样即使删除的是原头节点,也不需要单独写分支。时间复杂度 O(n),空间复杂度 O(1)

完整版教学

一、为什么需要快慢指针

链表不能像数组一样通过下标直接访问倒数第 N 个节点。最直观的方法是先遍历一遍求长度,再计算正数位置,再遍历第二遍删除。快慢指针的价值在于用“距离差”代替“长度计算”,从而一趟扫描完成。快指针先走 n 步后,慢指针和快指针之间就保持了固定间隔。

记忆钩子:倒数问题不要急着数长度,先想“让一个指针领先 N 步”。

二、间隔为什么刚好能定位前驱节点

假设链表长度是 5,要删除倒数第 2 个节点,也就是第 4 个节点。

dummy -> 1 -> 2 -> 3 -> 4 -> 5
              slow      delete
                        fast 到尾部

如果 fast 先领先 2 步,然后一起走到 fast.next == null,慢指针会停在删除节点的前一个节点。删除链表节点时必须拿到前驱节点,因为单向链表无法从当前节点回到前一个节点。

三、为什么要用 dummy

如果要删除的是头节点,例如链表 [1,2,3] 删除倒数第 3 个,真实答案是 [2,3]。没有 dummy 时,头节点没有前驱,代码需要单独处理 head = head.next。加上 dummy 后,头节点也有了统一的前驱节点。

情况没有 dummy有 dummy
删除中间节点正常改前驱指针正常改前驱指针
删除尾节点正常改前驱指针正常改前驱指针
删除头节点需要特判统一处理

dummy 的作用不是改变链表含义,而是让边界情况变普通。

四、代码模板

标准写法如下:

function removeNthFromEnd(head, n) {
  const dummy = { next: head }
  let fast = dummy
  let slow = dummy

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

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

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

注意循环条件是 fast.next !== null,因为我们希望 slow 停在待删除节点的前驱,而不是待删除节点本身。

五、边界例子怎么手算

链表 [1] 删除倒数第 1 个。dummy -> 1,fast 先走一步到 1,此时 fast.next == null,slow 仍在 dummy。执行 slow.next = slow.next.next 后,dummy 指向 null,返回空链表。

链表 [1,2] 删除倒数第 1 个。fast 先到 1,同步走到 2,slow 到 1,删除 slow.next 正好删除尾节点。

这些小例子能验证模板没有偏一位。

六、常见误区与追问

  • 误区:让 fast 先走 n + 1 步但循环条件不改。 领先步数和停止条件必须配套,否则慢指针会偏一位。
  • 误区:不用 dummy 然后忘记删除头节点。 倒数第 N 个可能就是第一个节点,必须覆盖这个边界。
  • 误区:让 slow 停在待删除节点。 单向链表删除需要前驱节点,停在当前节点反而不好改链接。
  • 追问:能不能两趟遍历? 可以,先求长度再删除,复杂度仍是 O(n),但一趟快慢指针更简洁。
  • 追问:如果 n 非法怎么办? LeetCode 类题通常保证合法;工程代码应检查 n > 0 且不超过长度。

这些追问的重点是链表删除依赖前驱节点,而不是只找到目标节点。

七、加强记忆

这题记成“dummy 保头,fast 领先,slow 找前驱”。dummy 解决删除头节点;fast 先走 n 步制造倒数距离;同步移动时保持这个距离;fast 到最后一个节点时,slow 正好在待删除节点前面。只要领先步数和停止条件配套,整题就不会乱。