LeetCode第83题:删除排序链表中的重复元素

103 阅读5分钟

LeetCode第83题:删除排序链表中的重复元素

题目描述

给定一个已排序的链表的头 head ,删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。

难度

简单

问题链接

删除排序链表中的重复元素

示例

示例 1:

输入:head = [1,1,2]
输出:[1,2]

示例 2:

输入:head = [1,1,2,3,3]
输出:[1,2,3]

提示

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序排列

解题思路

这道题要求删除链表中的重复元素,使每个元素只出现一次。由于链表已经排序,所以相同的元素一定相邻。

方法:一次遍历

我们可以通过一次遍历来解决这个问题:

  1. 如果链表为空或只有一个节点,则直接返回链表
  2. 使用一个指针 current 遍历链表
  3. 对于每个节点,检查其值是否与下一个节点的值相同
    • 如果相同,则将当前节点的 next 指向下下个节点,跳过重复的节点
    • 如果不同,则将 current 指针向后移动一位
  4. 重复步骤 3,直到遍历完整个链表
  5. 返回链表的头节点

关键点

  • 由于链表已经排序,相同的元素一定相邻,所以只需要比较当前节点与下一个节点的值
  • 当发现重复元素时,不需要移动当前指针,因为删除重复节点后,当前节点的下一个节点可能仍然与当前节点的值相同
  • 当前节点与下一个节点的值不同时,才将当前指针向后移动

算法步骤分析

步骤操作说明
1处理边界情况如果链表为空或只有一个节点,则直接返回链表
2初始化指针current = head
3遍历链表current 不为空且 current.next 不为空时,检查当前节点
4检查重复如果当前节点与下一个节点的值相同,则删除下一个节点
5更新指针如果没有重复,则将 current 向后移动一位
6返回结果返回链表的头节点

算法可视化

以示例 1 为例,head = [1,1,2]

步骤current操作链表状态
初始1初始状态1 -> 1 -> 2
11current.val == current.next.val,删除下一个节点1 -> 2
21current.val != current.next.val,将 current 向后移动1 -> 2
32current.next == null,结束遍历1 -> 2
结束2返回链表的头节点1 -> 2

以示例 2 为例,head = [1,1,2,3,3]

步骤current操作链表状态
初始1初始状态1 -> 1 -> 2 -> 3 -> 3
11current.val == current.next.val,删除下一个节点1 -> 2 -> 3 -> 3
21current.val != current.next.val,将 current 向后移动1 -> 2 -> 3 -> 3
32current.val != current.next.val,将 current 向后移动1 -> 2 -> 3 -> 3
43current.val == current.next.val,删除下一个节点1 -> 2 -> 3
53current.next == null,结束遍历1 -> 2 -> 3
结束3返回链表的头节点1 -> 2 -> 3

代码实现

C# 实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     public int val;
 *     public ListNode next;
 *     public ListNode(int val=0, ListNode next=null) {
 *         this.val = val;
 *         this.next = next;
 *     }
 * }
 */
public class Solution {
    public ListNode DeleteDuplicates(ListNode head) {
        // 处理边界情况
        if (head == null || head.next == null) {
            return head;
        }
        
        ListNode current = head;
        
        while (current != null && current.next != null) {
            // 如果当前节点与下一个节点的值相同,则删除下一个节点
            if (current.val == current.next.val) {
                current.next = current.next.next;
            } else {
                // 如果不同,则将当前指针向后移动
                current = current.next;
            }
        }
        
        return head;
    }
}

Python 实现

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        # 处理边界情况
        if not head or not head.next:
            return head
        
        current = head
        
        while current and current.next:
            # 如果当前节点与下一个节点的值相同,则删除下一个节点
            if current.val == current.next.val:
                current.next = current.next.next
            else:
                # 如果不同,则将当前指针向后移动
                current = current.next
        
        return head

C++ 实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        // 处理边界情况
        if (!head || !head->next) {
            return head;
        }
        
        ListNode* current = head;
        
        while (current && current->next) {
            // 如果当前节点与下一个节点的值相同,则删除下一个节点
            if (current->val == current->next->val) {
                ListNode* temp = current->next;
                current->next = current->next->next;
                delete temp; // 释放内存
            } else {
                // 如果不同,则将当前指针向后移动
                current = current->next;
            }
        }
        
        return head;
    }
};

执行结果

C# 执行结果

  • 执行用时:88 ms,击败了 94.74% 的 C# 提交
  • 内存消耗:38.1 MB,击败了 89.47% 的 C# 提交

Python 执行结果

  • 执行用时:40 ms,击败了 95.24% 的 Python3 提交
  • 内存消耗:14.8 MB,击败了 92.86% 的 Python3 提交

C++ 执行结果

  • 执行用时:8 ms,击败了 93.33% 的 C++ 提交
  • 内存消耗:11.5 MB,击败了 90.00% 的 C++ 提交

代码亮点

  1. 简单直观:算法思路简单明了,易于理解和实现。
  2. 一次遍历:只需要遍历链表一次,时间复杂度为 O(n)。
  3. 原地修改:不需要额外的空间,直接在原链表上进行修改,空间复杂度为 O(1)。
  4. 边界条件处理:正确处理了链表为空或只有一个节点的情况。
  5. 内存管理:在 C++ 实现中,释放了被删除节点的内存,避免内存泄漏。

常见错误分析

  1. 指针更新错误:在删除节点后,没有正确更新指针,可能导致死循环或访问空指针。
  2. 边界条件处理不当:没有考虑链表为空或只有一个节点的情况。
  3. 内存泄漏:在 C++ 实现中,如果不释放被删除的节点,可能导致内存泄漏。
  4. 移动指针的时机:在删除节点后,不应该立即移动当前指针,因为删除后的下一个节点可能仍然与当前节点的值相同。
  5. 循环条件设置错误:循环条件应该是 current != null && current.next != null,确保不会访问空指针。

解法比较

解法时间复杂度空间复杂度优点缺点
一次遍历O(n)O(1)简单直观,原地修改
递归法O(n)O(n)代码简洁空间复杂度较高,可能导致栈溢出
哈希表法O(n)O(n)可以处理未排序的链表需要额外空间,且没有利用链表已排序的特性

相关题目