19. 删除链表的倒数第 N 个结点
当然可以!这段代码是 LeetCode 中一个非常经典的题目:删除链表的倒数第 N 个节点(Remove Nth Node From End of List)。
🧩题目要求:
给定一个单链表,删除链表的倒数第 n
个节点,并返回链表的头节点。
📌题解思路:
使用 双指针法(快慢指针) + 虚拟头节点(dummy node)。
🧱代码分析:
ListNode dummy = new ListNode(0);
dummy.next = head;
-
创建一个虚拟头节点
dummy
,它的next
指向head
。 -
用于简化边界处理,比如删除第一个节点时不用单独判断。
if (head == null || head.next == null) return null;
-
如果链表为空或者只有一个节点,直接返回
null
,因为删掉最后一个节点之后就没有节点了。
ListNode fast = head;
ListNode slow = head;
while (--n > 0) fast = fast.next;
-
初始化快慢指针。
-
快指针先走
n - 1
步(因为一开始两个指针都在head
,这相当于间隔n
个节点)。
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
dummy = dummy.next;
}
-
快指针和慢指针一起走,直到快指针走到最后一个节点。
-
此时
slow
指向的是 要被删除节点的前一个节点。 -
这段代码的问题是:你用了
dummy = dummy.next;
,这其实让 dummy 跟着慢指针跑了,不太合理。dummy
应该是一直指向链表头的哨兵,不该动。
if (slow == head) return slow.next;
-
如果要删除的是第一个节点,直接返回
head.next
。
dummy.next = slow.next;
return head;
-
正常情况下,跳过当前的
slow
节点,完成删除。
⚠️这段代码的问题:
-
dummy 不应该动。你写了
dummy = dummy.next;
,这是不对的,dummy 应该固定。 -
判断
slow == head
有点冗余,用 dummy 可以避免这种边界判断。 -
实际上,只需要固定 dummy,fast 从 dummy 开始走 n 步,再 fast 和 slow 一起走到尾部。
✅更标准写法如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
// fast 先走 n 步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
// 然后 fast 和 slow 一起走,直到 fast 到尾
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 删除 slow 的下一个节点
slow.next = slow.next.next;
return dummy.next;
}
}
🌟总结:
-
使用 dummy 可以避免很多边界问题。
-
快慢指针配合可以高效定位到倒数第 N 个节点。
-
本题的核心技巧是:让 fast 先走 n 步,再一起走。
好的!我们来画一个简单的链表图,帮助你理解 快慢指针删除倒数第 N 个节点 的过程。
🧷 假设链表如下(5个节点):
索引: 1 → 2 → 3 → 4 → 5
节点: [1]→[2]→[3]→[4]→[5]→null
我们要 删除倒数第 2 个节点(也就是值为 4 的节点)。
📌 添加一个虚拟头节点 dummy:
dummy → [1]→[2]→[3]→[4]→[5]→null
初始状态:
fast 和 slow 都指向 dummy
🚶♂️fast 先走 n 步(n = 2):
第一步:
fast → [1] slow → dummy
第二步:
fast → [2] slow → dummy
🏃♂️fast 和 slow 一起走,直到 fast 到达最后一个节点:
第一轮:
fast → [3] slow → [1]
第二轮:
fast → [4] slow → [2]
第三轮:
fast → [5] slow → [3]
结束循环,此时:
slow → [3]
✂️删除 slow 的下一个节点:
slow.next = slow.next.next;
也就是把 [4]
从链表中跳过,指向 [5]
:
dummy → [1]→[2]→[3]→[5]→null
✅最终返回 dummy.next
即是新的头节点:
[1]→[2]→[3]→[5]→null