问题描述
给定一个已排序的链表的头节点 head,删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。
示例 :
输入:head = [1,1,2,3,3]
输出:[1,2,3]
问题分析
这是一个经典的链表操作问题。由于链表已经排序,所有重复的元素都会相邻出现。我们需要遍历链表,当遇到重复元素时,跳过重复的节点。
关键点:
-
链表已排序,重复元素必然相邻
-
只需要保留每个元素的第一个出现
-
需要修改指针来跳过重复节点
解决方案
方法一:直接遍历法
这是最直观的解法。我们使用一个指针遍历链表,比较当前节点和下一个节点的值:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// 如果链表为空或只有一个节点,直接返回
if (head == null || head.next == null) {
return head;
}
ListNode cur = head;
while (cur != null && cur.next != null) {
if (cur.val == cur.next.val) {
// 跳过重复节点
cur.next = cur.next.next;
} else {
// 移动到下一个不重复的节点
cur = cur.next;
}
}
return head;
}
}
代码解析
直接遍历法详细解析
-
边界条件处理:
java
if (head == null || head.next == null) { return head; }空链表或只有一个节点的链表不需要去重。
-
遍历链表:
java
ListNode cur = head; while (cur != null && cur.next != null) { // 处理逻辑 }使用
cur指针遍历链表,需要同时检查cur和cur.next是否为空。 -
处理重复元素:
java
if (cur.val == cur.next.val) { // 跳过重复节点 cur.next = cur.next.next; } else { // 移动到下一个不重复的节点 cur = cur.next; }-
如果当前节点和下一个节点的值相同,修改
cur.next指针,跳过下一个节点 -
如果值不同,正常移动到下一个节点
-
复杂度分析
时间复杂度: O(n)
- 只需要遍历链表一次,每个节点最多被访问一次
空间复杂度:
- 直接遍历法:O(1),只使用了常数级别的额外空间
常见错误
错误示例
java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
while (cur != null) {
ListNode p = cur.next;
if (p.val != cur.val) { // 可能空指针异常
cur = p;
}
cur.next = p.next; // 逻辑混乱
}
return head;
}
}
错误分析:
-
空指针异常 :当
cur.next为null时,p为null,访问p.val会导致空指针异常 -
逻辑错误 :在
if语句内部移动cur,然后又在外部修改cur.next,逻辑不清晰 -
可能跳过不重复节点 :当
p.val != cur.val时,代码将cur移动到p,然后立即修改cur.next = p.next,这会跳过p节点
扩展思考
如果链表未排序怎么办?
如果链表未排序,我们需要使用额外的数据结构(如HashSet)来记录已经出现的元素。时间复杂度仍然是O(n),但空间复杂度变为O(n)。
如何删除所有重复元素(LeetCode 82)?
LeetCode 82要求删除所有重复出现的元素,只保留没有重复出现的元素。这需要更复杂的指针操作,因为需要完全删除重复元素,而不仅仅是去重。
总结
删除排序链表中的重复元素是一个基础的链表操作问题,关键在于理解指针的操作。直接遍历法是最常用的解法,具有O(1)的空间复杂度。递归解法虽然简洁,但有O(n)的空间复杂度。在实际面试中,建议使用直接遍历法,并注意处理边界条件。
关键技巧:
-
使用双指针或单指针遍历链表
-
比较相邻节点的值
-
通过修改指针来跳过重复节点
-
注意处理空指针异常
掌握了这个问题,可以帮助你更好地理解链表的基本操作,为更复杂的链表问题打下基础。