链表——删除链表的倒数第N个节点

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

这是经典快慢指针(双指针),最优解法O(n)一遍遍历+虚拟头节点

核心思路(一遍遍历,不用求长度)

  1. 虚拟头节点dummy(防止删除第一个节点时出错)
  2. 快指针先走n步
  3. 然后快慢一起走,直到快指针到末尾
  4. 此时慢指针的下一个就是要删除的节点

完整代码实现如下:

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:

  1. fast 先走 2 步
  2. 然后 fast、slow 一起走,fast 走到最后一个节点停
  3. 此时 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) 仅用几个指针
posted @ 2026-04-08 15:37  ZealousMclaren  阅读(11)  评论(0)    收藏  举报