LeetCode 82. 删除排序链表中的重复元素 II
题目描述


题意分析
给定一个按非递减顺序排列的链表,删除所有出现次数超过一次的值所对应的节点,只保留在原链表中恰好出现一次的值。某个值一旦重复,它的所有节点都要删除,不能留下其中一个。
返回处理后的头节点,保留节点的顺序不变。输入可能为空,重复段也可能位于头部、尾部或连续出现;若每个值都重复,结果就是空链表。链表已经有序,所以相同值一定连在一起,可以按连续的一段来判断是否保留。
解法:哨兵节点跳过重复段
核心思路
[!blue]
在头节点前添加哨兵
dummy,用pre指向已经确认保留部分的最后一个节点。初始还没有保留任何节点,令pre = dummy;每轮需要判断的第一个节点是pre.next。若
pre.next与它的后继值不同,当前值在这一段中只出现一次。由于链表有序,相同值不可能隔开出现;此前的段也已经完整处理,所以这个节点可以保留,让pre前进一步。若两者值相同,说明遇到了重复段。先保存重复值
duplicate,再不断执行pre.next = pre.next.next,直到pre.next为空或值不再等于duplicate。这样会删掉这一段的所有节点,包括最开始发现的那个节点。删除期间以及删除之后,
pre都不前进:它仍是已保留部分的末尾,而新接上的pre.next尚未判断,也可能属于另一段重复值。只有确定某个节点应当保留,才能推进pre。当未处理部分只剩零个或一个节点时,已无法形成新的重复段;之前遇到的重复值又都已整段删除,因此剩下的单个节点可以直接保留。最终从
dummy.next取得结果,头部是否被删除不需要额外分支。
解题步骤
- 创建指向
head的哨兵,令pre = dummy。- 当
pre.next和pre.next.next都存在时,比较它们的值。- 若不同,保留
pre.next,执行pre = pre.next。- 若相同,记录重复值;只要
pre.next存在且值等于它,就把该节点从连接中跳过。删除整段后保持pre不变,继续检查新接上的节点。- 未处理部分不足两个节点时结束,返回
dummy.next。
代码实现
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy;
while (pre.next != null && pre.next.next != null) {
if (pre.next.val != pre.next.next.val) {
pre = pre.next;
} else {
// 记下重复值,整段都删除,前驱暂时不移动。
int duplicate = pre.next.val;
// 重复段可能延伸到末尾,每次读取前先检查节点存在。
while (pre.next != null && pre.next.val == duplicate) {
pre.next = pre.next.next;
}
}
}
return dummy.next;
}
}
func deleteDuplicates(head *ListNode) *ListNode {
dummy := &ListNode{Next: head}
pre := dummy
for pre.Next != nil && pre.Next.Next != nil {
if pre.Next.Val != pre.Next.Next.Val {
pre = pre.Next
} else {
// 记下重复值,整段都删除,前驱暂时不移动。
duplicate := pre.Next.Val
// 重复段可能延伸到末尾,每次读取前先检查节点存在。
for pre.Next != nil && pre.Next.Val == duplicate {
pre.Next = pre.Next.Next
}
}
}
return dummy.Next
}
复杂度分析
- 时间复杂度:$O(n)$,
n为节点数。每个节点要么被pre越过并保留,要么被跳过并删除;内层循环只消耗当前重复段,不会重新扫描已经处理的节点。- 空间复杂度:$O(1)$,只使用哨兵、前驱指针和重复值变量。
关键点总结
[!green]
- 利用有序性,把相同值视为一个连续段:长度为一就保留,长度大于一就整段删除。
pre始终指向已确认保留部分的末尾,pre.next才是下一轮的待判节点。- 保留一个节点时推进
pre,删除一段时只修改pre.next。
易错点总结
[!yellow]
- 只删掉后面的重复节点会留下一个重复值,不符合本题“只保留原来出现一次的值”的要求。
- 删除整段后直接推进
pre,会跳过新接上的节点,导致连续的重复段处理不全。- 必须先保存重复值,再修改连接;否则判断基准可能随
pre.next改变,把后面的其他值一起删除。- 读取节点值之前先判空,因为重复段可能一直延伸到末尾。
- 原头节点可能被删除,返回值必须是
dummy.next。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 83. 删除排序链表中的重复元素 | 简单 | 原题把每段重复值保留一个,本题只要某值重复就整段删除,前驱推进规则不同。 |
| 203. 移除链表元素 | 简单 | 同样用哑节点与前驱删除一段节点,原题按指定值删除,本题先识别重复段。 |
| 19. 删除链表的倒数第 N 个结点 | 中等 | 用哨兵与前驱节点统一链表删改边界;本题删除整段重复值,该题先用指针间隔定位倒数节点。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!