删除链表的倒数第 N 个结点(LeetCode 19)
题目链接:删除链表的倒数第 N 个结点(LeetCode 19)
难度:中等
1. 题目描述
给你一个链表,删除链表的 倒数第 n 个结点,并且返回链表的头结点。
要求:
- 链表中每个结点的值都是唯一的
- 1 <= 链表长度 <= 30
- 1 <= n <= 链表长度
- 题目保证
n是有效的(即一定能删除)
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1], n = 1
输出:[]
输入:head = [1,2], n = 1
输出:[1]
2. 问题分析
2.1 规律
- 链表是单向的,无法直接从尾部访问。
- 倒数第
n个结点 → 正数第L - n + 1个(L为链表长度),但计算长度需要两遍遍历。 - 核心问题:如何一次遍历找到倒数第
n个结点?
2.2 快慢指针(双指针)思路
使用 快慢指针 技巧,一次遍历解决:
- 让
fast先走n步。 - 然后
slow和fast同时向前走,直到fast到达末尾(fast.next == null)。 - 此时
slow恰好位于 倒数第n+1个结点 (因为fast比slow超前n步)。 - 删除
slow.next即可。
特殊处理:
- 如果删除的是头结点(即
n等于链表长度),直接返回head.next。 - 使用 虚拟头结点(dummy head) 可统一处理所有情况,避免单独判断头结点。
3. 代码实现
Python
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 创建虚拟头结点,简化删除头结点的逻辑
dummy = ListNode(0, head)
fast = slow = dummy
# fast 先走 n 步
for _ in range(n):
fast = fast.next
# fast 和 slow 一起走,直到 fast 到末尾
while fast.next:
fast = fast.next
slow = slow.next
# slow.next 就是要删除的结点
slow.next = slow.next.next
return dummy.next
C++
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 虚拟头结点
ListNode* dummy = new ListNode(0, head);
ListNode* fast = dummy;
ListNode* slow = dummy;
// fast 先走 n 步
for (int i = 0; i < n; ++i) {
fast = fast->next;
}
// 一起走,直到 fast 到最后一个结点
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
// 删除 slow 的下一个结点
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
ListNode* result = dummy->next;
delete dummy; // 可选:释放虚拟头结点
return result;
}
};
4. 复杂度分析
- 时间复杂度 :O(L),其中
L是链表长度。只遍历一次。 - 空间复杂度:O(1),只使用了常数额外空间(虚拟头结点可忽略)。
5. 总结
- 倒数第 n 个 → 快慢指针 是标配
- 使用 虚拟头结点 统一处理边界(删除头结点)
- 类似题目:
- 环形链表 II(快慢指针找入口)
- 链表中点(LeetCode 876)
- 可扩展:删除倒数第
n个后 重建链表 或 返回被删结点值
复习
面试经典150题[003]:删除有序数组中的重复项(LeetCode 26)
面试经典150题[033]:最小覆盖子串(LeetCode 76)
面试经典150题[048]:汇总区间(LeetCode 228)