(LeetCode-Hot100)19. 删除链表的倒数第 N 个结点

删除链表的倒数第 N 个结点

🔗 LeetCode 中文链接

📌 问题简介

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

题目描述

给定一个链表,删除链表的倒数第 n 个节点,并返回链表的头节点。

提示

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

📌 示例说明

示例 1:

复制代码
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

复制代码
输入:head = [1], n = 1
输出:[]

示例 3:

复制代码
输入:head = [1,2], n = 1
输出:[1]

💡 解题思路

方法一:双指针(快慢指针)✅(推荐)

这是最经典、高效的方法,满足"一趟扫描"的要求。

步骤如下

  1. 创建一个虚拟头节点 dummy,指向原链表头。这样可以统一处理删除头节点的情况。
  2. 定义两个指针 fastslow,初始都指向 dummy
  3. 先让 fast 指针向前走 n + 1 步(注意是 n+1,因为我们要让 slow 停在待删除节点的前一个位置)。
  4. 然后 fastslow 同时向前移动,直到 fast 到达链表末尾(即 fast == null)。
  5. 此时 slow 指向的是倒数第 n+1 个节点(即待删除节点的前驱),执行 slow.next = slow.next.next 即可删除目标节点。
  6. 返回 dummy.next

为什么是 n+1 步?

因为我们希望 slow 最终停在要删除节点的前一个节点,这样才能修改其 next 指针。


方法二:先计算长度再删除 ❌(不满足进阶要求)

  1. 第一次遍历:计算链表总长度 L
  2. 第二次遍历:从头走到第 L - n 个节点(即待删除节点的前一个),然后删除。
  3. 时间复杂度 O(2L) ≈ O(L),但需要两趟扫描。

虽然可行,但不符合"一趟扫描"的进阶要求,不推荐。


方法三:栈(辅助空间)⚠️

  1. 将所有节点依次入栈。
  2. 弹出 n 个节点,此时栈顶即为待删除节点的前驱。
  3. 修改指针并返回。

空间复杂度 O(L),不如双指针优雅。


💻 代码实现

java 复制代码
// Java 实现:双指针法
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        ListNode fast = dummy;
        ListNode slow = dummy;
        
        // fast 先走 n+1 步
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }
        
        // fast 和 slow 同时前进,直到 fast 为 null
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        
        // 删除倒数第 n 个节点
        slow.next = slow.next.next;
        
        return dummy.next;
    }
}
go 复制代码
// Go 实现:双指针法
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    dummy := &ListNode{Val: 0, Next: head}
    fast, slow := dummy, dummy

    // fast 先走 n+1 步
    for i := 0; i <= n; i++ {
        fast = fast.Next
    }

    // fast 和 slow 同时前进
    for fast != nil {
        fast = fast.Next
        slow = slow.Next
    }

    // 删除节点
    slow.Next = slow.Next.Next

    return dummy.Next
}

🧪 示例演示(以示例1为例)

原始链表:1 → 2 → 3 → 4 → 5n = 2

  1. 构造虚拟头:dummy → 1 → 2 → 3 → 4 → 5
  2. fast 先走 3 步(n+1=3):fast 指向 4
  3. slowdummyfast4
  4. 同步移动:
    • fast=5, slow=1
    • fast=null, slow=3
  5. 此时 slow 指向 3slow.next4(即倒数第2个)
  6. 执行 slow.next = slow.next.next3 → 5
  7. 结果:1 → 2 → 3 → 5

✅ 答案有效性证明

  • 边界情况覆盖

    • 删除头节点(如 [1,2], n=2):dummy 机制确保正确处理。
    • 删除唯一节点(如 [1], n=1):slow 指向 dummydummy.next = null,返回 null
    • 删除尾节点(如 n=1):slow 停在倒数第二个,正确删除最后一个。
  • 指针逻辑正确性

    • fastn+1 步后,与 slow 的距离恒为 n+1
    • fast 到达 null(链表尾后一位),slow 必在倒数第 n+1 位。

因此,算法在所有合法输入下均正确。


📊 复杂度分析

方法 时间复杂度 空间复杂度 是否一趟扫描
双指针(推荐) O(L) O(1) ✅ 是
计算长度 O(L) O(1) ❌ 否
O(L) O(L) ✅ 是

L 为链表长度。


📌 问题总结

  • 核心技巧 :使用虚拟头节点避免对头节点的特殊处理。
  • 关键洞察 :通过控制两个指针的距离(n+1),实现"定位倒数第 n 个节点的前驱"。
  • 最佳实践:双指针法时间空间最优,且满足进阶要求。
  • 易错点
    • 忘记 n+1 步,导致 slow 停在待删除节点而非其前驱。
    • 未使用虚拟头,导致删除头节点时逻辑复杂。

💡 一句话口诀快指针先走 n+1,慢指针随后跟;快到终点时,慢删下一结。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
QC班长8 分钟前
Maven公司私库配置踩坑点
java·服务器·maven·intellij-idea
Makoto_Kimur11 分钟前
java开发面试-AI Coding速成
java·开发语言
审判长烧鸡35 分钟前
Go命名规则【2】全场景命名避坑指南
go·命名规则·ai问答
wuqingshun31415941 分钟前
说说mybatis的缓存机制
java·缓存·mybatis
知识浅谈42 分钟前
DeepSeek V4 和 GPT-5.5 在同一天发布了??我也很懵,但对比完我悟了
算法
DeepModel1 小时前
通俗易懂讲透 Q-Learning:从零学会强化学习核心算法
人工智能·学习·算法·机器学习
田梓燊1 小时前
力扣:19.删除链表的倒数第 N 个结点
算法·leetcode·链表
空中海1 小时前
Kubernetes 生产实践、可观测性与扩展入门
java·贪心算法·kubernetes
Devin~Y1 小时前
大厂Java面试实录:Spring Boot/Cloud、Kafka、Redis、K8s 与 Spring AI(RAG/Agent)三轮连环问
java·spring boot·redis·mysql·spring cloud·kafka·kubernetes
bLEd RING2 小时前
SpringBoot3.3.0集成Knife4j4.5.0实战
java