【力扣100题】15.删除链表的倒数第 N 个结点

1. 题目描述

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

示例

复制代码
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
解释:删除倒数第2个结点(值为4)

输入:head = [1], n = 1
输出:[]
解释:删除倒数第1个结点(唯一的结点),链表为空

输入:head = [1,2], n = 1
输出:[1]
解释:删除倒数第1个结点(值为2)

提示

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

进阶

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


2. 核心思想

关键思想:快慢指针 + 虚拟头节点

要删除倒数第 n 个结点,需要找到它的前一个结点

核心思路:让快指针先走 n 步,然后快慢指针一起走,当快指针走到末尾时,慢指针正好在目标结点的前一个位置。

复制代码
以 [1,2,3,4,5], n=2 为例:

初始:dummy → 1 → 2 → 3 → 4 → 5 → nullptr
             ↑           ↑
             slow        fast

第1步:fast 走 n=2 步
       fast = 3(走了1步)
       fast = 4(走了2步)

第2步:fast 和 slow 同时走
       fast=4, slow=1  → fast->next=5, slow=2
       fast=5, slow=2  → fast->next=nullptr, slow=3
       fast->next=null,退出

此时:slow 在结点 3 的位置
      slow->next 是结点 4(要删除的)
      slow->next = slow->next->next,删除成功!

3. 多种方法解决

方法一:快慢指针 + 虚拟头节点(推荐)✅

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;
        
        // 第一步:快指针先走 n 步
        while (n--) {
            fast = fast->next;
        }
        
        // 第二步:快慢指针同时走
        ListNode* slow = dummy;
        while (fast->next) {
            slow = slow->next;
            fast = fast->next;
        }
        
        // 第三步:删除目标结点
        ListNode* toDelete = slow->next;
        slow->next = slow->next->next;
        delete toDelete;
        
        return dummy->next;
    }
};

复杂度: 时间 O(L),空间 O(1),只遍历一次 ✅


方法二:计算链表长度(两次遍历)

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        
        // 第一次遍历:计算链表长度
        int length = 0;
        ListNode* curr = head;
        while (curr) {
            length++;
            curr = curr->next;
        }
        
        // 第二次遍历:找到倒数第 n 个结点的前一个
        length -= n;  // 正数第 length-n+1 个就是要删的
        curr = dummy;
        while (length-- > 1) {
            curr = curr->next;
        }
        
        // 删除
        ListNode* toDelete = curr->next;
        curr->next = curr->next->next;
        delete toDelete;
        
        return dummy->next;
    }
};

复杂度: 时间 O(L),空间 O(1),但需要遍历两次 ❌


方法三:栈(不推荐)

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        stack<ListNode*> stk;
        
        ListNode* curr = dummy;
        while (curr) {
            stk.push(curr);
            curr = curr->next;
        }
        
        // 弹出 n 个,剩下的栈顶就是要删结点的前一个
        for (int i = 0; i < n; i++) {
            stk.pop();
        }
        
        ListNode* prev = stk.top();
        prev->next = prev->next->next;
        
        return dummy->next;
    }
};

复杂度: 时间 O(L),空间 O(L) ❌


4. 图解过程

示例:head = [1,2,3,4,5], n = 2

复制代码
初始状态:
dummy(0) → 1 → 2 → 3 → 4 → 5 → nullptr
 ↑
fast=dummy, slow=dummy

第一步:fast 先走 n=2 步
       fast = 0→next = 1(走1步)
       fast = 1→next = 2(走2步)

第二步:fast 和 slow 同时走,直到 fast→next == nullptr
       
       fast=2, slow=dummy
       fast->next = 3 ≠ nullptr,继续
       
       fast=3, slow=1
       fast->next = 4 ≠ nullptr,继续
       
       fast=4, slow=2
       fast->next = 5 ≠ nullptr,继续
       
       fast=5, slow=3
       fast->next = nullptr,退出

此时:
slow = 3,slow->next = 4(要删除的结点)

删除操作:
slow->next = slow->next->next
即:3→next = 5

最终:
dummy → 1 → 2 → 3 → 5 → nullptr

边界情况:head = [1], n = 1

复制代码
初始:dummy(0) → 1 → nullptr

fast 先走1步:
fast = 1(走完)

fast->next = nullptr,while 不执行

slow = dummy
slow->next = 1(要删除)

删除后:
dummy → nullptr
return dummy->next = nullptr ✓

5. 方法优缺点比较

方法 时间 空间 优点 缺点
快慢指针 O(L) ✅ O(1) ✅ 一趟扫描,指针移动最少 思维稍难
计算长度 O(L) O(1) 简单直观 需要两次遍历
O(L) O(L) 代码简单 空间开销大

推荐方法

方法一(快慢指针) 是面试和竞赛中的标准解法:

  1. 一趟扫描,时间最优
  2. O(1) 空间
  3. 掌握后代码很简洁

6. 完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

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;
        
        while (n--) {
            fast = fast->next;
        }
        
        ListNode* slow = dummy;
        while (fast->next) {
            slow = slow->next;
            fast = fast->next;
        }
        
        ListNode* toDelete = slow->next;
        slow->next = slow->next->next;
        delete toDelete;
        
        return dummy->next;
    }
};

// 辅助函数:链表转 vector(方便打印)
vector<int> toVector(ListNode* head) {
    vector<int> result;
    while (head) {
        result.push_back(head->val);
        head = head->next;
    }
    return result;
}

// 辅助函数:vector 转链表
ListNode* toList(vector<int> vals) {
    ListNode* dummy = new ListNode(0);
    ListNode* curr = dummy;
    for (int v : vals) {
        curr->next = new ListNode(v);
        curr = curr->next;
    }
    return dummy->next;
}

int main() {
    Solution sol;

    // 测试1:[1,2,3,4,5], n=2 → [1,2,3,5]
    ListNode* h1 = toList({1, 2, 3, 4, 5});
    vector<int> r1 = toVector(sol.removeNthFromEnd(h1, 2));
    cout << "[1,2,3,4,5], n=2 → [";
    for (int i = 0; i < r1.size(); i++)
        cout << r1[i] << (i < r1.size()-1 ? "," : "");
    cout << "]" << endl;

    // 测试2:[1], n=1 → []
    ListNode* h2 = toList({1});
    vector<int> r2 = toVector(sol.removeNthFromEnd(h2, 1));
    cout << "[1], n=1 → [";
    for (int i = 0; i < r2.size(); i++)
        cout << r2[i] << (i < r2.size()-1 ? "," : "");
    cout << "]" << endl;

    // 测试3:[1,2], n=1 → [1]
    ListNode* h3 = toList({1, 2});
    vector<int> r3 = toVector(sol.removeNthFromEnd(h3, 1));
    cout << "[1,2], n=1 → [";
    for (int i = 0; i < r3.size(); i++)
        cout << r3[i] << (i < r3.size()-1 ? "," : "");
    cout << "]" << endl;

    return 0;
}

输出:

复制代码
[1,2,3,4,5], n=2 → [1,2,3,5]
[1], n=1 → []
[1,2], n=1 → [1]

8. 总结

要点 说明
核心思想 快慢指针:快指针先走 n 步,然后一起走到末尾
虚拟头节点 统一处理头结点删除,无需单独判断
关键 slow 停在目标结点的前一个位置
复杂度 时间 O(L),空间 O(1),一趟扫描 ✅
相关推荐
leoufung14 小时前
LeetCode 50. Pow(x, n):从 O(n) 到 O(log n) 的快速幂彻底搞懂
算法·leetcode·职场和发展
@小码农15 小时前
2026年信息素养大赛【星火征途】图形化编程复赛和决赛模拟题B
开发语言·数据结构·c++·算法
人道领域15 小时前
【LeetCode刷题日记】347.前k个高频元素
java·数据结构·算法·leetcode
七颗糖很甜15 小时前
台风数据免费获取教程
大数据·python·算法
AI科技星15 小时前
《全域数学》第一部·数术本源
算法·机器学习·数学建模·数据挖掘·量子计算
此生决int15 小时前
快速复习之数据结构篇——链表
数据结构·链表
阿Y加油吧15 小时前
二刷 LeetCode:118. 杨辉三角 & 198. 打家劫舍 复盘笔记
笔记·算法·leetcode
深邃-15 小时前
【数据结构与算法】-二叉树(1):树的概念与结构,二叉树的概念与结构
数据结构·算法·链表·二叉树··顺序表
风筝在晴天搁浅15 小时前
手撕归并排序
数据结构·算法·排序算法
lynnlovemin15 小时前
C++高精度加减乘除算法详解
开发语言·c++·算法·高精度