19. 删除链表的倒数第 N 个结点(中等)
题目
给定一条单链表,删除「从表尾往前数第 n 个」结点,并返回新链表的头结点。
这里的 n 从 1 开始:n = 1 表示最后一个结点,n = 链表长度 表示头结点。
自造示例:head = [10,20,30,40],n = 3 时从尾部倒数依次是 40(第 1 个)、30(第 2 个)、20(第 3 个),所以删除 20,输出 [10,30,40];若 n = 4,则删除头结点,输出 [20,30,40]。
约束:结点数 sz 满足 1 ≤ sz ≤ 30;结点值 0 ≤ Node.val ≤ 100;1 ≤ n ≤ sz。
思路
一句话:先让快指针走出 n 步的间隔,再让两个指针同步前进,快指针走到链表末尾之外时,慢指针刚好停在待删结点的前一个位置。
先想最直白的做法:把链表遍历一遍放进数组,按「倒数第 n 个」就是「正数第 sz - n + 1 个」来删除。这当然能做,但需要 O(sz) 额外空间;如果只是先用一遍遍历数出长度 sz,再用第二遍走到 sz - n 的位置删除,可以省掉数组,但要扫描两遍。
关键观察:链表不能像数组一样随机下标访问,但「倒数第 n 个」可以用两个指针的固定间隔表示——快指针先走 n 步,两针之间就隔了 n 个结点;之后两针同速前进,快指针越过最后一个结点、变成 nullptr / None 时,慢指针正好停在待删结点的前驱。
再补一个工程细节:如果待删的是头结点,头结点没有前驱,会让代码出现特判。引入一个虚拟头结点 dummy,让慢指针从 dummy 出发,就能把所有情况统一成「删除中间结点」。
解法
设链表结点数为 sz,步骤如下:
- 建虚拟头
dummy,令dummy.next = head。 - 左指针
left从dummy出发,右指针right从head出发。 right先走n步。left与right同步前进,直到right越出链表末尾。- 此时
left在待删结点前一个位置,执行left.next = left.next.next。 - 返回
dummy.next。
以 [10,20,30,40]、n = 3 为例:
| 阶段 | left | right | 说明 |
|---|---|---|---|
| 初始化 | dummy | 10 | le本篇为付费内容您已阅读试读部分,购买后即可解锁全文。 ⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。 |
还没有评论,来说两句?