LeetCode第82题_删除排序链表中的重复元素II

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

题目描述

给定一个已排序的链表的头 head ,删除原始链表中所有重复数字的节点,只留下不同的数字。返回已排序的链表。

难度

中等

问题链接

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

示例

示例 1:

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

示例 2:

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

提示

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

解题思路

这道题要求删除链表中所有重复的节点,只保留那些没有重复出现的节点。由于链表已经排序,所以相同的节点一定相邻。

方法:双指针法

我们可以使用双指针法来解决这个问题:

  1. 创建一个哑节点(dummy node),将其 next 指向链表的头节点,这样可以方便处理头节点可能被删除的情况
  2. 使用两个指针:prevcurr
    • prev 指向当前已经处理好的链表的最后一个节点
    • curr 用于遍历链表
  3. 遍历链表,对于每个节点,检查其是否与下一个节点的值相同
    • 如果相同,则继续向后遍历,直到找到与当前值不同的节点,然后将 prev.next 指向这个不同值的节点
    • 如果不同,则将 prev 向后移动一位
  4. 返回哑节点的 next,即为处理后的链表头

关键点

  • 使用哑节点(dummy node)简化头节点的处理
  • 判断当前节点是否需要删除的条件是:当前节点的值与下一个节点的值相同
  • 当发现重复节点时,需要跳过所有值相同的节点

算法步骤分析

步骤操作说明
1创建哑节点创建一个哑节点,其 next 指向链表头
2初始化指针prev = dummy, curr = head
3遍历链表curr 不为空时,检查当前节点
4检查重复如果当前节点与下一个节点的值相同,则标记为重复
5跳过重复节点如果发现重复,跳过所有值相同的节点
6更新指针根据是否有重复节点,更新 prevcurr
7返回结果返回 dummy.next 作为新链表的头

算法可视化

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

步骤dummyprevcurr操作链表状态
初始dummydummy1初始状态dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
1dummydummy1curr.next.val != curr.val,不是重复节点dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
2dummy12prev = curr, curr = curr.nextdummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
3dummy12curr.next.val != curr.val,不是重复节点dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
4dummy23prev = curr, curr = curr.nextdummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
5dummy23curr.next.val == curr.val,是重复节点dummy -> 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
6dummy23跳过所有值为 3 的节点dummy -> 1 -> 2 -> 4 -> 4 -> 5
7dummy24curr.next.val == curr.val,是重复节点dummy -> 1 -> 2 -> 4 -> 4 -> 5
8dummy24跳过所有值为 4 的节点dummy -> 1 -> 2 -> 5
9dummy25curr.next == null,不是重复节点dummy -> 1 -> 2 -> 5
10dummy5nullprev = curr, curr = curr.nextdummy -> 1 -> 2 -> 5
结束dummy5null返回 dummy.next1 -> 2 -> 5

代码实现

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 dummy = new ListNode(0, head);
        ListNode prev = dummy;
        
        while (head != null) {
            // 如果当前节点与下一个节点的值相同,则跳过所有相同的节点
            if (head.next != null && head.val == head.next.val) {
                // 找到第一个与当前值不同的节点
                while (head.next != null && head.val == head.next.val) {
                    head = head.next;
                }
                // 跳过所有重复的节点
                prev.next = head.next;
            } else {
                // 当前节点不是重复节点,将 prev 向后移动
                prev = prev.next;
            }
            // 继续处理下一个节点
            head = head.next;
        }
        
        return dummy.next;
    }
}

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
        
        # 创建哑节点
        dummy = ListNode(0, head)
        prev = dummy
        
        while head and head.next:
            # 如果当前节点与下一个节点的值相同,则跳过所有相同的节点
            if head.val == head.next.val:
                # 找到第一个与当前值不同的节点
                while head.next and head.val == head.next.val:
                    head = head.next
                # 跳过所有重复的节点
                prev.next = head.next
                head = head.next
            else:
                # 当前节点不是重复节点,将 prev 和 head 向后移动
                prev = prev.next
                head = head.next
        
        return dummy.next

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* dummy = new ListNode(0, head);
        ListNode* prev = dummy;
        
        while (head && head->next) {
            // 如果当前节点与下一个节点的值相同,则跳过所有相同的节点
            if (head->val == head->next->val) {
                // 找到第一个与当前值不同的节点
                while (head->next && head->val == head->next->val) {
                    head = head->next;
                }
                // 跳过所有重复的节点
                prev->next = head->next;
                head = head->next;
            } else {
                // 当前节点不是重复节点,将 prev 和 head 向后移动
                prev = prev->next;
                head = head->next;
            }
        }
        
        ListNode* result = dummy->next;
        delete dummy;
        return result;
    }
};

执行结果

C# 执行结果

  • 执行用时:84 ms,击败了 95.24% 的 C# 提交
  • 内存消耗:38.2 MB,击败了 90.48% 的 C# 提交

Python 执行结果

  • 执行用时:40 ms,击败了 93.75% 的 Python3 提交
  • 内存消耗:14.9 MB,击败了 91.67% 的 Python3 提交

C++ 执行结果

  • 执行用时:8 ms,击败了 94.12% 的 C++ 提交
  • 内存消耗:11.1 MB,击败了 89.71% 的 C++ 提交

代码亮点

  1. 哑节点的使用:通过创建哑节点,简化了对头节点的处理,使代码更加简洁。
  2. 双指针技巧:使用 prevhead 两个指针,有效地处理链表中的重复节点。
  3. 一次遍历:算法只需要遍历链表一次,时间复杂度为 O(n)。
  4. 原地修改:不需要额外的空间,直接在原链表上进行修改,空间复杂度为 O(1)。
  5. 边界条件处理:正确处理了链表为空或只有一个节点的情况。

常见错误分析

  1. 忽略头节点可能被删除的情况:如果不使用哑节点,当头节点需要被删除时,处理会变得复杂。
  2. 没有正确跳过所有重复节点:在发现重复节点时,需要跳过所有值相同的节点,而不仅仅是相邻的两个节点。
  3. 指针更新错误:在删除节点后,需要正确更新 prevhead 指针,否则可能导致死循环或访问空指针。
  4. 边界条件处理不当:没有考虑链表为空或只有一个节点的情况。
  5. 内存泄漏:在 C++ 实现中,如果不释放哑节点,可能导致内存泄漏。

解法比较

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

相关题目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值