LeetCode Hot100(20/100)——19. 删除链表的倒数第 N 个结点

文章目录

    • 题目描述
    • 核心难点与通用技巧
      • 难点分析
      • 核心技巧:哨兵节点(Dummy Node)
    • 解题思路总览
    • 最佳实践:双指针法(一次遍历)
      • 原理讲解
      • 详细步骤流程
      • 算法流程图
      • 过程时序演示 (以 1->2->3->4->5, n=2 为例)
    • Java 代码实现
    • 复杂度分析
      • 时间复杂度
      • 空间复杂度
    • 总结

在链表操作中,这是一道非常经典的题目。它考察了我们对链表特性的理解,特别是如何在无法预知链表长度且只能单向遍历的情况下,精准定位目标节点。

题目描述

给你一个链表的头结点 head 和一个整数 n,请你删除链表中倒数第 n 个结点,并返回链表的头结点。

示例:

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

核心难点与通用技巧

难点分析

单向链表只能向后遍历,无法像数组一样通过索引直接访问,也无法像双向链表一样从尾部向前遍历。要删除"倒数"第 N 个节点,我们必须找到该节点的前驱节点(即倒数第 N+1 个节点)。

核心技巧:哨兵节点(Dummy Node)

在处理链表删除操作时,最容易出现问题的就是删除头结点 的情况。如果倒数第 N 个刚好是头结点,我们需要特殊的逻辑来更新 head。

为了简化代码,通用的技巧是创建一个 dummy 节点(哑节点/哨兵节点),将其 next 指向 head。这样,无论删除的是哪个节点,我们都有一个确定的"前驱节点",最后只需返回 dummy.next 即可。
引入哨兵节点
Dummy
1
2
3


解题思路总览

我们可以通过以下思维导图来梳理主要的解题策略:
删除倒数第N个节点
计算链表长度法
原理: 先遍历求长度L,再走L-n步
优点: 直观,易理解
缺点: 需要两次遍历
一次遍历
原理: 快慢指针维护固定间距
优点: 效率高,只需一次遍历
关键: 快指针先走n+1步
栈辅助法
原理: 利用栈的先进后出特性
优点: 逻辑简单
缺点: O(N)的额外空间

考虑到面试和工程实践中的最优解,本文将重点讲解 双指针法(快慢指针),这是本题的"标准答案"。


最佳实践:双指针法(一次遍历)

原理讲解

我们要找到倒数第 n 个节点的前一个节点。我们可以利用两个指针 fastslow,让它们之间保持一定的"距离"。

想象一把尺子,长度为 n

  1. fast 指针先出发。
  2. fast 走了 n 步(或者 n+1 步,取决于实现细节)之后,slow 指针从头开始出发。
  3. 两个指针以相同的速度向后移动。
  4. fast 指针到达链表末尾(null)时,由于两者距离恒定,slow 指针刚好停留在我们需要的位置。

详细步骤流程

为了方便删除,我们需要让 slow 指针停在 倒数第 n+1 个节点(即目标节点的前驱)。

  1. 初始化 dummy 节点指向 head
  2. 初始化 fastslow 都指向 dummy
  3. 关键步骤 :让 fast 先向后移动 n + 1 步。
  4. 同时移动 fastslow,直到 fast 指向 null
  5. 此时 slow 指向的正是待删除节点的前驱节点。
  6. 执行删除操作:slow.next = slow.next.next

算法流程图



开始
初始化 Dummy 节点指向 Head
Fast, Slow 均指向 Dummy
Fast 指针先向前移动 n+1 步
Fast 是否为 null?
Fast 和 Slow 同时前移 1 步
Slow 到达目标节点的前驱
删除操作: slow.next = slow.next.next
返回 dummy.next

过程时序演示 (以 1->2->3->4->5, n=2 为例)

我们来看一下指针的移动过程:
Null Node 5 Node 4 Node 3 Node 2 Node 1 Dummy Null Node 5 Node 4 Node 3 Node 2 Node 1 Dummy 初始状态: Fast=Dummy, Slow=Dummy, n=2 第一阶段: Fast 先走 n+1 (3) 步 第二阶段: 两者同速移动 loop [直到 Fast == Null] 此时 Slow 在 Node 3 (倒数第3个节点) 删除 Node 4 (3.next = 5) Fast 移动到 Node 3 Fast 移至 4 Slow 移至 1 Fast 移至 5 Slow 移至 2 Fast 移至 Null Slow 移至 3


Java 代码实现

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 1. 创建哨兵节点,处理删除头结点的极端情况
        ListNode dummy = new ListNode(0, head);
        
        // 2. 初始化快慢指针
        ListNode fast = dummy;
        ListNode slow = dummy;
        
        // 3. 让 fast 指针先走 n+1 步
        // 为什么要走 n+1 步?
        // 因为我们希望 slow 最后停在倒数第 n+1 个节点(即待删除节点的前驱)
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }
        
        // 4. 同时移动两个指针,直到 fast 到达末尾
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        
        // 5. 此时 slow 指向的是待删除节点的前一个节点
        // 执行删除操作
        slow.next = slow.next.next;
        
        // 6. 返回新的头结点
        return dummy.next;
    }
}

复杂度分析

时间复杂度

O(L),其中 L 是链表的长度。

  • fast 指针遍历了链表一次。
  • slow 指针遍历了链表 L-n 次。
  • 总体来说,我们对链表进行了一次遍历操作。

空间复杂度

O(1)

  • 我们需要常数级别的额外空间来存储 dummyfastslow 变量,没有使用随链表长度增长的额外结构(如数组或栈)。

总结

解决"删除链表倒数第 N 个节点"的关键点在于:

  1. 哨兵节点 (Dummy Node) :极大地简化了边界条件的处理,特别是当需要删除头节点时,不需要单独编写 if 逻辑。
  2. 双指针 (Two Pointers) :利用 fastslow 之间的固定间距(Gap),将"寻找倒数第 N 个"转化为"寻找正数第 L-N 个",且无需预先计算长度 L。

这种快慢指针/双指针的技巧在链表题目中非常常用(例如找中点、判断环),掌握它对于提升算法能力至关重要。

相关推荐
hrrrrb2 小时前
【算法设计与分析】随机化算法
人工智能·python·算法
进击的小头2 小时前
一阶IIR低通滤波器:从原理到嵌入式实战
c语言·算法
2301_811232982 小时前
C++中的契约编程
开发语言·c++·算法
2401_829004022 小时前
C++中的访问者模式
开发语言·c++·算法
青槿吖2 小时前
第二篇:JDBC进阶骚操作:防注入、事务回滚、连接池优化,一篇封神
java·开发语言·jvm·算法·自动化
sin_hielo2 小时前
leetcode 1984
数据结构·算法·leetcode
古城小栈3 小时前
开发常用 宏
算法·rust
m0_748248653 小时前
C语言向C++过渡
c语言·c++·算法