← 返回题目列表

如何删除单链表的倒数第 N 个节点?

高频 中等 第 11 / 28 题 更新于 2026/07/29
链表双指针哑结点

简化版

双指针 + 哑结点一次遍历:先让 fast 从头先走 N 步,再让 fastslow 一起走,等 fast 到末尾时,slow 正好停在「倒数第 N+1 个」——也就是待删节点的前驱,改一下指针即可。加一个哑结点是为了统一处理「删的是头节点」的情况。时间 O(n)、空间 O(1)。

详细版

ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);  // 哑结点,指向 head
    dummy.next = head;
    ListNode fast = dummy, slow = dummy;
    for (int i = 0; i < n; i++) fast = fast.next; // fast 先走 n 步
    while (fast.next != null) {         // 一起走到 fast 是最后一个节点
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;          // slow 是待删节点的前驱,跳过它
    return dummy.next;                    // 用 dummy.next 返回,兼容删头
}

要删一个节点,就得拿到它的前驱。让快慢指针保持 N 步的间距,快指针到末尾时,慢指针恰好在待删节点的前一个,直接 slow.next = slow.next.next 删除。

完整版教学

一、为什么让两个指针差 N 步

「倒数第 N 个」等价于「正数第 len−N+1 个」,但我们不想先遍历一遍求 len 再走一遍。让快指针先走 N 步,此时快慢之间恰好隔 N 步;之后两者同速前进,这个 N 步的间距保持不变。当快指针到达末尾(走了 len 步),慢指针走了 len−N 步,正好停在倒数第 N 个的前驱。一次遍历完成。

二、哑结点(dummy)解决了什么

如果要删的正好是头节点(倒数第 N 个 == 第一个),那它没有前驱,普通写法要特判。加一个哑结点 dummy 挂在 head 前面,让快慢指针都从 dummy 出发,这样「待删节点的前驱」永远存在(哪怕删头,前驱就是 dummy),代码不用为头节点单独写分支。最后返回 dummy.next 而不是 head,因为头可能已经被删掉了。

三、边界与易错点

  • 快指针先走 N 步后要不要判越界:如果 N 等于链表长度,快指针走 N 步后正好指向末尾的下一个(配合 dummy 的写法是走到最后一个真实节点)。用了 dummy 的上面写法能自然覆盖「删头」,一般不会越界;但若题目不保证 N 合法,需加判断。
  • 返回值:一定用 dummy.next,别直接 return head
  • 只走一趟就删掉,不要「先数长度再走一半」,那样是两趟。

四、这套「间距双指针」的通用性

「让两个指针保持固定间距」是链表双指针的一大类:倒数第 N 个、链表相交的对齐、滑动窗口等都用到「固定间距/固定窗口 + 同步移动」的思想。掌握它,很多「一次遍历定位某个相对位置」的题都能秒解。

五、走位例子与删除位置

以链表 1→2→3→4→5n=2 为例,目标是删除倒数第 2 个节点 4。使用 dummy→1→2→3→4→5 后,fast 先走 2 步到节点 2,然后 fastslow 同步走,直到 fast.next == null。此时 fast=5slow=3slow.next 正好是要删的 4,执行 slow.next = slow.next.next 后得到 1→2→3→5

情况目标节点dummy 的作用
n=1删除尾节点slow 停在尾节点前驱
n=len删除头节点slow 停在 dummy,统一删除头
1<n<len删除中间节点slow 停在目标前驱

这题真正要定位的不是“倒数第 N 个节点本身”,而是它的前驱;单链表删除必须拿到前驱才能改 next

dummy -> 1 -> 2 -> 3 -> 4 -> 5 统一看三类边界:

删倒数第 5 个:slow=dummy,跳过 1,返回 dummy.next=2
删倒数第 3 个:slow=2,跳过 3,链表变成 1->2->4->5
删倒数第 1 个:slow=4,跳过 5,4.next 变成 null

这三个例子说明:只要 slow 停在前驱,删除动作永远是同一行 slow.next = slow.next.next,不会因为目标在头部、中间或尾部而改变核心逻辑。

六、常见误区与追问

  • 误区:快指针先走 N+1 步才对。 是否走 N 或 N+1 取决于 fast/slow 从哪里出发、循环停在 fast 还是 fast.next;关键是不变量要让 slow 最后停在待删节点前驱。
  • 误区:不用 dummy 也一样简单。 不用 dummy 时删除头节点必须特判;dummy 能把删头、删中间、删尾统一成同一行指针修改。
  • 误区:返回原来的 head 就行。 如果删除的是头节点,原 head 已经失效,必须返回 dummy.next
  • 追问:如果 n 大于链表长度怎么办? 若题目不保证合法,需要在 fast 先走阶段检测 null 并按约定返回或报错。
  • 追问:为什么只遍历一遍? 快慢指针同步阶段到尾时,慢指针已由固定间距自然定位到目标前驱,不需要先求长度。
  • 追问:删除尾节点会不会空指针?n=1 时 slow 停在尾节点前驱,slow.next.next 为 null,赋值后前驱直接指向 null,是合法删除尾部。

七、加强记忆

删倒数第 N 个 = 双指针拉开 N 步间距 + 哑结点兜底删头:快指针先走 N 步,再一起走到末尾,慢指针即待删节点前驱,slow.next = slow.next.next,最后返回 dummy.next。一次遍历、O(1) 空间。