题目描述:
给你一个链表,删除链表的倒数第
n个结点,并且返回链表的头结点。示例 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]
解题思路:
方法一:暴力解法
我的思路:先把链表的值按顺序存入 vector,删除倒数第 n 个,再用剩下的值重建一个新链表。
具体步骤:
边界处理:如果链表只有一个节点,直接返回
nullptr取值:遍历链表,把所有值存入
temp删除:从
temp中删除倒数第 n 个重建:用
temp中的值新建链表节点,连接成新链表返回:返回新链表的头节点
用例子走一遍:
head = 1 -> 2 -> 3 -> 4 -> 5,n = 2
| 步骤 | 操作 | 结果 |
|---|---|---|
| 1 | 遍历链表 | temp = [1, 2, 3, 4, 5] |
| 2 | 删除倒数第2个(下标 5-2=3) | temp = [1, 2, 3, 5] |
| 3 | 重建链表 | 1 -> 2 -> 3 -> 5 |
| 4 | 返回 | 1 -> 2 -> 3 -> 5✅ |
代码实现:
class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { if(head->next==nullptr) return nullptr; vector<int> temp; while(head!=nullptr){ temp.push_back(head->val); head=head->next; } temp.erase(temp.begin() + (temp.size() - n)); ListNode* h=nullptr; ListNode* t=nullptr; for(int x:temp){ ListNode* node=new ListNode(x); if(h==nullptr){ h=node; t=node; } else{ t->next=node; t=node; } } return h; } };复杂度分析:
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 遍历链表一次 O(n) + 重建链表一次 O(n) |
| 空间复杂度 | O(n) | temp存储 n 个值 + 新建 n-1 个节点 |
缺点:
❌空间复杂度 O(n):需要额外存储所有值和新建所有节点
❌没有利用链表的特性:链表题应该用指针操作,而不是转成数组
方法二:快慢指针(O(1) 空间)
核心思路:
fast先走n+1步fast和slow同时走,当fast到末尾时,slow正好在要删除节点的前一个直接
slow->next = slow->next->next完成删除
代码实现:
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 同时走 while (fast != nullptr) { fast = fast->next; slow = slow->next; } // 删除 slow->next slow->next = slow->next->next; return dummy->next; } };复杂度:
- 时间 O(n)
- 空间 O(1)
两种方法的对比:
| 对比维度 | 暴力解法 | 最优解法(快慢指针) |
|---|---|---|
| 时间复杂度 | O(n) | O(n) |
| 空间复杂度 | O(n) | O(1) |
| 是否新建节点 | ✅ 需要 new n-1 个 | ❌ 不需要 |
| 是否修改原链表 | ❌ 不修改 | ✅ 修改 |
| 代码长度 | 较长 | 中等 |