链表——删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。、
这是经典快慢指针(双指针),最优解法O(n)一遍遍历+虚拟头节点
核心思路(一遍遍历,不用求长度)
- 虚拟头节点dummy(防止删除第一个节点时出错)
- 快指针先走n步
- 然后快慢一起走,直到快指针到末尾
- 此时慢指针的下一个就是要删除的节点
完整代码实现如下:
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);
dummy->next = head;
ListNode* fast = dummy;
ListNode* slow = dummy;
// 快指针先走 n 步
for (int i = 0; i < n; ++i) {
fast = fast->next;
}
// 快慢一起走,直到快到最后一个节点
while (fast->next != nullptr) {
fast = fast->next;
slow = slow->next;
}
// 删除 slow->next
slow->next = slow->next->next;
return dummy->next;
}
};
假设链表长度 5,n=2:
- fast 先走 2 步
- 然后 fast、slow 一起走,fast 走到最后一个节点停
- 此时 slow 正好在「要删除节点的前一个]
为什么必须这样?
因为我们要删除节点,必须拿到它前面的节点,否则无法改指针。
为什么必须用 dummy(虚拟头结点)?
如果要删除的是第一个节点:
- 没有dummy:head = head->next,要特判
- 有dummy:slow最后就是dummy,直接dummy->next = dummy->next->next
完全不用任何特判,代码统一、安全、简洁
特判的意思是:
不是head->next会空指针错误
而是:
如果没有dummy,头节点没有前驱,删除头节点必须必须单独处理,即:head = head->next,代码不统一
dummy 的作用只有一个:
让所有节点(包括第一个)都拥有前一个节点,统一删除逻辑,不用特判。
复杂度
- 时间:O(n) 一遍遍历
- 空间:O(1) 仅用几个指针
浙公网安备 33010602011771号