给你一个链表,删除链表的倒数第 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]
进阶:你能尝试使用一趟扫描实现吗?
解题思路
核心:快慢指针 + 虚拟头结点dummy
- fast指针先向前走n步;
- 之后fast、slow指针一起向后移动,直到fast->next == NULL;
- 此时slow正好停在待删除节点的前驱结点;
- 修改slow->next = slow->next->next,跳过目标节点,完成删除。
- 返回dummy->next作为新链表头。
原理图解
以链表 1→2→3→4→5,n=2举例
C语言完整代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
// 建立虚拟头结点,处理删除头节点边界
struct ListNode dummy;
dummy.next = head;
struct ListNode* fast = &dummy;
struct ListNode* slow = &dummy;
// fast指针先走n步
for(int i = 0; i < n; i++){
fast = fast->next;
}
// fast和slow一起移动,直到fast的下一个为空
while(fast->next != NULL)
{
fast = fast->next;
slow = slow->next;
}
// 删除slow后面的节点
slow->next = slow->next->next;
return dummy.next;
}
关键坑点解析
坑1:为什么一定要虚拟头结点dummy?
测试用例 head=[1], n=1:需要删除第一个(也就是唯一的)节点。 如果没有dummy,slow没有前驱节点,删除头结点逻辑要单独写if判断。 引入dummy虚拟头,所有删除场景逻辑统一,不需要特殊分支。
坑2:循环条件为什么是 fast->next != NULL,不是 fast != NULL
- 如果写while(fast != NULL),slow最终会走到待删除节点本身,我们拿不到前驱,无法完成链表跳过操作。
- fast->next==NULL停止循环,slow定位到待删节点的前一个节点,才可以修改next指针。
坑3:内存泄漏问题(LeetCode判题不会卡,但工程代码要注意)
上面代码做题可以直接AC,但是没有释放被删除节点的内存。 工程开发,删除节点建议补充释放内存:
struct ListNode* delNode = slow->next;
slow->next = slow->next->next;
free(delNode);
LeetCode OJ不校验内存泄漏,刷题可以省略;写项目务必加上free。
补充:普通两次遍历的思路(对比)
缺点:需要扫描链表两次;快慢指针方案只需要一趟遍历,满足题目的进阶要求。
总结
原题链接:19. 删除链表的倒数第 N 个结点 – 力扣(LeetCode)





