LeetCode 19. 删除链表的倒数第 N 个结点
📌 题目描述
题目级别:中等
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
- 示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
💡 破题思路:前后双指针 (拉开固定间距)
要想删除倒数第 n 个节点,关键在于如何精准定位到它的前驱节点 。
如果不允许先遍历一遍获取链表总长度,我们可以使用非常经典的**双指针(滑动窗口)**技巧:
- 引入 Dummy 节点 :删除节点时,最怕的就是删除的是头节点。引入一个虚拟头节点
dummy,让原链表的所有节点都变成"普通节点",消除边界痛点。 - 错开身位 :准备两个指针
pre和post。让post先走。 - 唤醒机制 (单循环魔法) :我们用目标值
n作为倒计时。每次循环post往前走,n减 1。当n <= 0时,说明post已经领先了n个身位 ,此时唤醒pre开始跟着一起走。 - 一击必杀 :这样一来,
pre和post之间就形成了一把长度恒定的"尺子"。当前面的post走到链表尽头时,后面的pre必然刚好停在倒数第n个节点的前面!直接修改pre->next即可完成击杀。
💻 C++ 代码实现 (原汁原味作者版 + 内存优化)
cpp
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 极客细节:在栈上分配 dummy 节点,避免 new 导致的内存泄漏
ListNode dummy(0, head);
// 双指针起跑线都设在 dummy
ListNode *pre = &dummy, *post = &dummy;
// 只要前面的探路者 post 还没走到尽头
while (post->next != nullptr)
{
// 当倒计时 n 归零或小于零时,唤醒 pre 同步移动
if (n <= 0)
{
pre = pre->next;
}
post = post->next;
n--;
}
// 此时 pre 刚好站在倒数第 n 个节点的前驱位置
// 直接跨过倒数第 n 个节点
pre->next = pre->next->next;
// 返回 dummy.next 即为真实头节点
return dummy.next;
}
};