题目描述
已排序链表,删除重复元素使每个值只保留一个。
思路解析
可以边走边把值存进集合,遇到已经存过的就删——但这要 O(n) 额外空间,还没用上「已排序」这个条件。排序链表明明重复值都相邻,根本不必记历史。
转折:既然重复值相邻,cur 只要盯住自己和下一个。若 cur.val 等于 cur.next.val,cur.next 是重复的,让 cur.next = cur.next.next 把它摘掉(cur 原地不动,继续比新的下一个);不同才让 cur 前进。一趟、O(1) 空间。下面一帧一个动作慢放。
起点 · cur=头:cur 指向第一个 1。准备拿它和下一个节点比较。
比较 · cur=1, next 也是 1 · 相同:cur(1) 和 cur.next(1) 相同!cur.next 这个重复的 1(del 标出)该被摘掉。
跳过重复 · cur.next = cur.next.next:执行 cur.next = cur.next.next:摘掉重复的 1(虚线划掉),cur 的箭头越过它直接接到 2。cur 不前进——新的 cur.next 是谁还没比过,可能仍重复。
再比较 · cur=1, next=2 · 不同:负例对照:cur 仍是 1,新的下一个是 2,两者不同。这说明 1 已经去重干净,不删——这一步什么都不摘,只决定要前进。
不同才前进 · cur 走到 2:因为不同,cur = cur.next 前进到 2。对比上面「相同时 cur 不动」——前进只发生在不删的时候。
比较 · cur=2, next=3 · 不同 → 前进:2 和下一个 3 不同,保留,cur 前进到第一个 3,继续比。
比较 · cur=3, next 也是 3 · 跳过:又一次相同:3 和下一个 3 重复,摘掉后一个 3(虚线划掉),cur 仍停在 3 不动。摘掉后它的 next 变成了 null。
完成 · cur.next 为 null · 停:cur.next 已是 null,循环条件 cur and cur.next 不成立、停止。保留下来的是 1 → 2 → 3,每个值只剩一个。
链表删除的通用细节:摘掉一个节点后当前指针别动,因为新接上的下一个可能仍要处理。前进只发生在「不删」时。
参考代码(Python)
1cur = head
2while cur and cur.next: # 要访问 cur.next.val 先确保它在
3 if cur.next.val == cur.val: # 相邻相同 = 重复
4 cur.next = cur.next.next # 跳过,cur 原地不动
5 else:
6 cur = cur.next # 不同才前进
7return head复杂度分析
- 时间复杂度:O(n) —— 一趟扫描,每个节点只被比较常数次
- 空间复杂度:O(1) —— 原地改指针,只用一个 cur,不开集合
套路模板
记住骨架:删除时只改指针、不前进;保留时才前进。这个「删/不删决定动不动」是链表删除题的统一节奏。
1cur = head
2while cur and cur.next:
3 if 该删(cur.next):
4 cur.next = cur.next.next # 删除,cur 不动
5 else:
6 cur = cur.next # 保留才前进易错点
- 错误写法:相同时写 cur.next=cur.next.next 后还 cur=cur.next → 正确写法:相同时只摘除、cur 保持不动(输入 1→1→1 时若摘完就前移,会跳过第二个 1,漏删一个)
- 错误写法:循环忘判 cur.next → 正确写法:while cur and cur.next(访问 cur.next.val 前必须确保 cur.next 存在,否则空指针)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。