在链表相关题目中,“删除节点”是一个看似简单但非常容易出错的操作。 本文结合我在刷题过程中的总结,梳理了链表删除的通用思路,并通过多个典型场景说明如何避免常见坑点,以及一些特殊处理
一、链表删除的本质在单链表中,节点只知道自己的 next,并不知道前驱节点。
因此,删除一个节点的核心操作永远是:
找到要删除节点的前一个节点 prev,然后:
1prev.next = prev.next.next;
这也是为什么:
头节点删除
倒数第 N 个节点
条件删除多个节点
几乎都会用到 哨兵节点(dummy node)。
二、为什么要使用哨兵节点(Dummy Node)以删除头节点为例:
11 -> 2 -> 3
如果要删除 1,你会发现:
它没有前驱节点
需要单独写 if 判断
引入哨兵节点后:
1dummy -> 1 -> 2 -> 3
这样就能统一删除逻辑:
123456789101112ListNode dummy = new ListNode(0);dummy.next = head;ListNode cur = dummy;while (cur.next != null) { if (cur.next.val == target) { cur.next = cur.next.next; } else { cur = cur.next; }}return dummy.next;
不需要特判头节点
删除逻辑始终一致
三、典型删除场景总结1. 删除倒数第 N 个节点(双指针)核心思想: 让快指针先走 N 步,使快慢指针保持固定距离。
123456789101112131415161718192021ListNode dummy = new ListNode(0);dummy.next = head;ListNode fast = dummy;ListNode slow = dummy;// fast 先走 N 步for (int i = 0; i < n; i++) { fast = fast.next;}// 同时移动while (fast.next != null) { fast = fast.next; slow = slow.next;}// slow 指向待删除节点的前一个slow.next = slow.next.next;return dummy.next;
关键点:
删除的是 slow.next
dummy 能处理删除头节点的情况
2. 删除排序链表中的重复元素在删除重复节点时,一定要注意空指针问题:
1234567while (cur.next != null && cur.next.next != null) { if (cur.next.val == cur.next.next.val) { cur.next = cur.next.next; } else { cur = cur.next; }}
在操作节点 node.next,node.val 时一定确保节点不为空
3. 删除“在数组中出现过”的节点(HashSet)当删除条件来源于一个数组时,最优解是:
先把数组放进 HashSet(优化:预分配空间)
再遍历链表,O(1) 判断是否需要删除
123456789101112131415Set
时间复杂度: O(n)空间复杂度: O(n)
四、从 O(n²) 到 O(n):正难则反的思想在一道题中,我遇到了这样一个需求:
删除链表中 右侧存在比当前节点值更大元素的节点
直觉做法(超时)
对每个节点向右扫描
判断是否存在更大的值 👉 O(n²),直接超时
优化思路:正难则反
反转链表
问题转化为:
删除“左侧存在比当前节点更大的节点”
删除比最大值小的节点
再反转回来
最终复杂度:
时间:O(n)
空间:O(1)
五、总结
链表删除的核心永远是:操作前驱节点
需要操作头节点时,哨兵节点可以极大简化代码逻辑
双指针是处理“倒数第 N 个节点”的标准解法
当正向思路复杂时,要学会 反转链表 + 条件转换,运用之前写过的函数,拆解问题
六、相关代码本文涉及的所有代码与笔记,均已同步至我的 GitHub 算法仓库,作为 Java 后端校招过程中的学习记录。