
本文详解 LeetCode 第 83 题「删除排序链表中的重复元素」的常见逻辑错误,重点剖析 head != head.next 判断失效的根本原因,并提供两种规范、健壮的实现方案(双指针原地修改与虚拟头结点构建新链表)。
本文详解 leetcode 第 83 题「删除排序链表中的重复元素」的常见逻辑错误,重点剖析 `head != head.next` 判断失效的根本原因,并提供两种规范、健壮的实现方案(双指针原地修改与虚拟头结点构建新链表)。
在解决 LeetCode #83「删除排序链表中的重复元素」时,一个典型错误是误用引用比较替代值比较。例如以下代码片段:
if (head != head.next) { // ❌ 错误!这是引用比较,非空节点永远不等于其 next 引用
result.next = head;
head = head.next;
}
result = result.next;
该条件 head != head.next 始终为 true(除非 head.next == head,这在合法单向链表中绝不可能发生),因为它比较的是两个对象引用的内存地址,而非节点值。真正需要判断的是:当前节点值是否与下一个节点值不同,即 head.val != head.next.val —— 但前提是 head.next 不为 null。
此外,原逻辑存在流程控制缺陷:
- result = result.next 被无条件执行,导致即使跳过重复节点,结果链表仍被错误延伸;
- head = head.next 被包裹在 if 内,造成循环卡死或遗漏遍历。
✅ 正确解法一:原地修改(推荐,空间 O(1))
使用快慢指针思想,仅遍历一次,通过调整 next 指针跳过重复节点:
public ListNode deleteDuplicates(ListNode head) {
ListNode temp = head;
while (temp != null && temp.next != null) {
if (temp.val == temp.next.val) {
temp.next = temp.next.next; // 直接跳过重复节点
} else {
temp = temp.next; // 仅当值不同时才移动指针
}
}
return head;
}
✅ 正确解法二:虚拟头结点 + 构建新链表(逻辑更清晰)
避免修改原链表结构,显式构造去重后的新链表,需注意边界处理:
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
while (head != null) {
// 若为尾节点,或当前值 ≠ 下一节点值,则保留该节点
if (head.next == null || head.val != head.next.val) {
curr.next = head;
curr = curr.next;
}
head = head.next; // 每轮都推进 head,确保遍历完整
}
curr.next = null; // 断开最后残留连接(可选,但更严谨)
return dummy.next;
}
⚠️ 关键注意事项:
- 永远先判空:访问 head.next.val 前必须确保 head.next != null,否则抛 NullPointerException;
- 区分引用与值:链表操作中,==/!= 用于引用比较(如 head == null),而节点去重必须基于 val 的数值比较;
- 指针推进时机:head 必须在每次循环中推进(无论是否保留),而 result 或 curr 仅在确认保留节点时才移动;
- 无需额外存储:因输入链表已排序,重复元素必然相邻,只需局部比较,无需哈希表或额外空间。
两种解法时间复杂度均为 O(n),空间复杂度分别为 O(1) 和 O(1)(新链表复用原节点,未新建 ListNode 实例)。实际面试中,原地修改法更受青睐;而虚拟头结点法逻辑隔离性好,不易出错,适合快速验证思路。











