删除倒数第 N 个结点 图解题解
不知道链表多长,怎么找倒数第 N 个?fast 先走 N 步,然后两个指针一起走,fast 到尾 slow 就到位——一遍搞定。
删倒数第 N 个节点:fast 先走 N 步,然后 fast 和 slow 一起走,fast 到链表末尾时 slow 正好停在待删节点的前一个——两者始终差 N 格,fast 到头 slow 就到位了。前面加一个 dummy 虚拟头节点,这样就算要删的是第一个节点,slow 也有左邻居可以直接改 next 断链,不用单独处理边界。
这道题到底在问什么
- head
- 1→2→3→4→5, n=2
- 输出
- 1→2→3→5
最优解:一步一步想明白
- 3可以先求长度 L,再删第 L-n+1 个。双指针能一遍完成。
- 4用 dummy 处理删除头结点。fast 先走 n 步后,二者一起走,fast 到尾时 slow 正好停在待删节点的前一个(前驱)。
- 5startdummy 指向 head。fast 和 slow 都从 dummy 出发。
- 6gapn 等于 2,fast 从 dummy 先走到节点 2。slow 还在 dummy。
- 7movefast 走到 3,slow 走到 1。两者间距始终是 2。
- 8move再同步走一步,fast 到 4,slow 到 2。间距仍是 2。
- 9predecessorfast 到最后一个节点 5,停。slow 在节点 3,正好是待删节点 4 的前一个(前驱)。
- 10skip执行 slow.next = slow.next.next,把 4 从链表中绕过去。
- 11return删除后返回 dummy.next,得到 1→2→3→5。
- 14dummy 让删除头结点也不用特判。
- 17dummy saveshead=[1,2], n=2。fast 先走到 2,while 不动,slow 留在 dummy,跳过 head。
- 24这题学完不要乱跳,下一步去 对应专题 连刷同类题;卡住时用 AI 答疑追问“为什么这一步成立”,比只背答案更稳。
⚠️ 容易写错的地方
✗ 错:不用 dummy 删除头结点会麻烦
✓ 对:dummy.next=head
当删除的是原头结点时,slow 可以停在 dummy。
✗ 错:fast 先走 n+1 步和循环条件混用
✓ 对:统一:先走 n 步,再 while fast.next
两种写法都可,但不能混。
✗ 错:slow 停在待删节点本身
✓ 对:slow 要停在待删节点的前一个(前驱)
单链表删除需要改前一个(前驱)节点的 next。
完整代码(Python / C++ / Java)
Python
class Solution:
def removeNthFromEnd(self, head, n):
dummy = ListNode(0)
dummy.next = head
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.nextC++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode dummy(0);
dummy.next = head;
ListNode *fast = &dummy, *slow = &dummy;
for (int 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;
}
};Java
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
for (int 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;
}
}套路模板 · 倒数第 K 个 = 固定间距双指针
# 倒数第 k 个 → fast 先领先 k 步,再同步走
dummy = ListNode(0); dummy.next = head
fast = slow = dummy
for _ in range(k): # 1. 拉开 k 的固定间距
fast = fast.next
while fast.next: # 2. 同步走到 fast 触尾
fast = fast.next
slow = slow.next # slow 落在倒数第 k 的前驱
# 3. 此刻 slow.next 就是目标,按需删/读/改
return dummy.nextListNode dummy(0); dummy.next = head;
ListNode *fast = &dummy, *slow = &dummy;
for (int i = 0; i < k; i++) // 1. 拉开 k 的间距
fast = fast->next;
while (fast->next) { // 2. 同步走到触尾
fast = fast->next;
slow = slow->next; // slow 落在前驱
}
// 3. slow->next 即倒数第 k 个,按需处理
return dummy.next;ListNode dummy = new ListNode(0); dummy.next = head;
ListNode fast = dummy, slow = dummy;
for (int i = 0; i < k; i++) // 1. 拉开 k 的间距
fast = fast.next;
while (fast.next != null) { // 2. 同步走到触尾
fast = fast.next;
slow = slow.next; // slow 落在前驱
}
// 3. slow.next 即倒数第 k 个,按需处理
return dummy.next;复杂度
时间复杂度
O(n)
fast 最多走完整条链表
空间复杂度
O(1)
只用 dummy、fast、slow
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 删除倒数第 N 个结点 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么 dummy 必要?+
它统一删除头结点的情况,返回 dummy.next 即可。
这道题为什么用「快慢指针」,换最直接的暴力解会差在哪?+
快慢指针抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。