
本文深入剖析在原地删除已排序单链表中重复节点时的经典错误:为何直接跳过重复节点却仍残留尾部冗余值,并给出正确、鲁棒的解决方案及代码实现。
本文深入剖析在原地删除已排序单链表中重复节点时的经典错误:为何直接跳过重复节点却仍残留尾部冗余值,并给出正确、鲁棒的解决方案及代码实现。
在解决 LeetCode 第 83 题「Remove Duplicates from Sorted List」时,一个常见但易被忽视的陷阱是:误以为只要跳过重复节点即可完成去重,却忽略了链表末尾悬空指针的清理。
你最初的思路——用一个虚拟头节点 temp 和游标 curr 构建新链表,仅当 head.val != curr.val 时才将 head 接入 curr.next——逻辑看似合理,但在实际执行中会出错。以输入 [1,1,2,3,3] 为例:
- 初始:temp → None,curr 指向 temp(其 val=0,由 ListNode() 默认构造决定);
- 第一次迭代:1 ≠ 0 → curr.next = head(即 temp.next = node(1)),curr 移至该节点;
- 后续成功接入 2、第一个 3;
- 当 head 指向第二个 3 时,因 3 == curr.val,仅执行 head = head.next(此时 head 变为 None),循环终止;
- 关键问题来了:此时 curr 仍指向第一个 3,而它的 next 指针未被显式置空,仍保留着原来指向第二个 3 的引用!因此最终返回的链表为 1 → 2 → 3 → 3,而非预期的 1 → 2 → 3。
这就是你观察到“打印 curr 移动过程只输出 1、2、3,但最终结果多出一个 3”的根本原因:链表结构未被切断,只是遍历停止了。
✅ 正确做法是在每次跳过重复节点后(即 else 分支),立即断开 curr.next 的旧连接:
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
temp = ListNode() # 虚拟头节点
curr = temp
while head:
if head.val != curr.val:
curr.next = head
curr = curr.next
head = head.next
else:
head = head.next
curr.next = None # ✅ 关键修复:主动截断后续链接
return temp.next
⚠️ 注意事项:
- curr.next = None 必须放在 else 分支内(即跳过重复节点时),而非循环外——否则无法处理连续重复(如 [0,0,0]);
- 不可仅在循环末尾统一置空(如 curr.next = None 放在 while 外),因为 curr 在最后一次 if 中已移动,此时置空会误删有效尾节点;
- 本解法时间复杂度 O(n),空间复杂度 O(1),真正实现了原地修改,无需额外存储。
? 总结:链表操作中,“断开连接”与“建立连接”同等重要。尤其在原地重构时,务必检查所有 next 指针是否处于预期状态——宁可多一次赋值,不可留一处悬空。










