面试经典150题[063]:删除链表的倒数第 N 个结点(LeetCode 19)

删除链表的倒数第 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 步。
  • 然后 slowfast 同时向前走,直到 fast 到达末尾(fast.next == null)。
  • 此时 slow 恰好位于 倒数第 n+1 个结点 (因为 fastslow 超前 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)

相关推荐
CoderYanger11 分钟前
动态规划算法-简单多状态dp问题:16.买卖股票的最佳时机含手续费
开发语言·算法·leetcode·动态规划·1024程序员节
爱学java的ptt34 分钟前
206反转链表
数据结构·链表
java修仙传1 小时前
力扣hot100:最大子数组和
数据结构·算法·leetcode
Rock_yzh1 小时前
LeetCode算法刷题——54. 螺旋矩阵
数据结构·c++·学习·算法·leetcode·职场和发展·矩阵
什么时候才能变强1 小时前
k6面试高频问题
面试·职场和发展·k6
风止何安啊1 小时前
从 “牵线木偶” 到 “独立个体”:JS 拷贝的爱恨情仇(浅拷贝 VS 深拷贝)
前端·javascript·面试
漫天黄叶远飞1 小时前
地址与地基:在 JavaScript 的堆栈迷宫里,重新理解“复制”的哲学
前端·javascript·面试
CoderYanger2 小时前
C.滑动窗口-求子数组个数-越短越合法——3258. 统计满足 K 约束的子字符串数量 I
java·开发语言·算法·leetcode·1024程序员节
CoderYanger2 小时前
动态规划算法-路径问题:9.最小路径和
开发语言·算法·leetcode·动态规划·1024程序员节
月明长歌2 小时前
【码道初阶】一道经典的简单题:Boyer-Moore 多数投票算法|多数元素问题(LeetCode 169)
算法·leetcode·职场和发展