本文详解 leetcode「删除排序链表中重复元素」问题中一种典型错误实现的根本原因——未切断尾部残留指针导致重复节点残留,并给出可鲁棒运行的修正方案及完整教程式解析。
本文详解 leetcode「删除排序链表中重复元素」问题中一种典型错误实现的根本原因——未切断尾部残留指针导致重复节点残留,并给出可鲁棒运行的修正方案及完整教程式解析。
在处理已排序链表去重时,一个直观思路是:新建哑节点(dummy node),用 curr 指针维护新链表尾部,遍历原链表 head,仅当当前节点值不同于 curr.val 时才将其接入新链表。但如下代码虽逻辑看似合理,却会在输入 [1,1,2,3,3] 时输出 [1,2,3,3],而非预期的 [1,2,3]:
class Solution(object):
def deleteDuplicates(self, head):
if not head:
return None
temp = ListNode() # 哑节点
curr = temp
while head:
if head.val != curr.val:
curr.next = head
head = head.next
curr = curr.next
else:
head = head.next
return temp.next
问题根源在于指针残留:当 head 遍历至最后一个 3 时,因 curr.val == 3(此时 curr 指向前一个 3),跳过连接;head 继续前移变为 None,循环终止。但此时 curr.next 仍指向原链表中已被跳过的那个末尾 3 节点(即 curr.next 未被显式置空),导致该节点意外保留在结果链表中。
⚠️ 注意:仅在循环末尾加 curr.next = None(如 return temp.next 前)不可靠——例如输入 [0,0,0] 时,curr 始终未移动(因首节点 0 与 temp.val==0 相等),curr 仍停留在哑节点,此时 curr.next = None 会直接清空整个结果。
✅ 正确修复方式:在每次执行 else 分支(即跳过重复节点)时,立即切断 curr.next 的旧链接。因为只要进入 else,说明 head 当前节点不应被保留,而 curr 本身尚未更新,其 next 字段可能仍悬垂着已被跳过的冗余节点。因此,在 head = head.next 后追加 curr.next = None,确保 curr 的后续引用始终干净:
class Solution(object):
def deleteDuplicates(self, head):
if not head:
return None
temp = ListNode()
curr = temp
while head:
if head.val != curr.val:
curr.next = head
curr = curr.next # 先更新 curr,再推进 head
head = head.next
else:
head = head.next
curr.next = None # 关键修复:即时断开悬垂指针
return temp.next
? 关键细节优化:将 curr = curr.next 提前至 head = head.next 之前(如上所示),可避免 curr 滞后于实际有效节点位置,使逻辑更清晰。同时,curr.next = None 放在 else 中,覆盖了所有“不连接新节点”的场景,无论 curr 是否曾移动,均能安全清除潜在脏链接。
总结:链表操作中,显式管理 next 指针的生命周期至关重要。不能依赖循环自然终止来清理尾部——必须在每一次可能产生悬垂引用的操作后主动置空。本题的教训适用于所有需重构链表结构的场景:宁可多写一行 node.next = None,也不留一丝指针歧义。










