LeetCode 83题:删除排序链表中的重复元素【面试】

简介: LeetCode 83题:删除排序链表中的重复元素【面试】

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

python源码解读

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

题目描述

给定一个排序链表,删除所有含有重复数字的节点,只留下原始链表中 没有重复出现 的数字。

输入格式
  • head:链表的头节点。
输出格式
  • 返回处理后的链表的头节点。

示例

示例 1
输入: 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
输出: 1 -> 2 -> 5
示例 2
输入: 1 -> 1 -> 1 -> 2 -> 3
输出: 2 -> 3

解题思路

方法一:哨兵节点 + 双指针

解题步骤
  1. 使用哨兵节点:在链表前添加一个哨兵节点,简化边界条件处理。
  2. 双指针追踪:使用两个指针 prevcurrprev 指向最后一个不重复的节点,curr 用于遍历链表。
完整的规范代码
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
def deleteDuplicates(head):
    """
    使用哨兵节点和双指针删除所有重复的元素
    :param head: ListNode, 链表的头节点
    :return: ListNode, 修改后的链表头节点
    """
    sentinel = ListNode(0)  # 哨兵节点
    sentinel.next = head
    prev = sentinel  # 前指针
    while head:
        # 如果当前节点是重复节点
        if head.next and head.val == head.next.val:
            # 跳过当前节点的所有重复实例
            while head.next and head.val == head.next.val:
                head = head.next
            prev.next = head.next
        else:
            prev = prev.next
        head = head.next
    return sentinel.next
# 示例调用
# 创建链表函数及打印函数假定已定义
算法分析
  • 时间复杂度:(O(n)),其中 (n) 是链表的长度。我们需要遍历所有的元素最多两次。
  • 空间复杂度:(O(1)),使用了常数空间额外存储。

方法二:递归法

解题步骤
  1. 递归删除:使用递归函数删除重复的元素,如果当前元素与下一个元素重复,则跳过当前的所有重复元素。
完整的规范代码
def deleteDuplicates(head):
    """
    递归删除所有重复的元素
    :param head: ListNode, 链表的头节点
    :return: ListNode, 修改后的链表头节点
    """
    if not head or not head.next:
        return head
    
    if head.val == head.next.val:
        while head.next and head.val == head.next.val:
            head = head.next
        return deleteDuplicates(head.next)
    else:
        head.next = deleteDuplicates(head.next)
        return head
# 示例调用代码
# 创建链表函数及打印函数假定已定义
算法分析
  • 时间复杂度:(O(n)),递归会访问每个节点一次。
  • 空间复杂度:(O(n)),在最坏的情况下,递归栈的深度可以达到 (n)。

方法三:一次遍历

解题步骤
  1. 直接遍历:只使用一个指针遍历整个链表,根据当前节点和下一节点的值是否相同决定是否删除。
完整的规范代码
def deleteDuplicates(head):
    """
    一次遍历删除所有重复的元素
    :param head: ListNode, 链表的头节点
    :return: ListNode, 修改后的链表头节点
    """
    dummy = ListNode(0)
    dummy.next = head
    current = dummy
    while current.next and current.next.next:
        if current.next.val == current.next.next.val:
            val = current.next.val
            while current.next and current.next.val == val:
                current.next = current.next.next
        else:
            current = current.next
    return dummy.next
# 示例调用代码
# 创建链表函数及打印函数假定已定义
算法分析
  • 时间复杂度:(O(n)),一次遍历链表。
  • 空间复杂度:(O(1)),只使用了有限的额外空间。

方法四:哈希表记录法

解题步骤
  1. 使用哈希表记录频率:首先遍历整个链表,使用哈希表记录每个元素出现的频率。
  2. 根据哈希表重建链表:再次遍历链表,根据哈希表中记录的频率决定是否保留节点。
完整的规范代码
def deleteDuplicates(head):
    """
    使用哈希表记录频率,然后删除所有重复的元素
    :param head: ListNode, 链表的头节点
    :return: ListNode, 修改后的链表头节点
    """
    from collections import Counter
    counter = Counter()
    node = head
    while node:
        counter[node.val] += 1
        node = node.next
    dummy = ListNode(0)
    dummy.next = head
    current = dummy
    while current.next:
        if counter[current.next.val] > 1:
            current.next = current.next.next
        else:
            current = current.next
    return dummy.next
# 示例调用代码
# 创建链表函数及打印函数假定已定义
算法分析
  • 时间复杂度:(O(n)),两次遍历链表,一次建立哈希表,一次重建链表。
  • 空间复杂度:(O(n)),哈希表的大小取决于链表的长度。

方法五:快慢指针法

解题步骤
  1. 快慢指针:快指针用于探测重复元素,慢指针用于连接非重复元素。
完整的规范代码
def deleteDuplicates(head):
    """
    使用快慢指针删除所有重复的元素
    :param head: ListNode, 链表的头节点
    :return: ListNode, 修改后的链表头节点
    """
    dummy = ListNode(0)
    dummy.next = head
    slow = dummy
    while head:
        if head.next and head.val == head.next.val:
            # 跳过当前的重复元素
            while head.next and head.val == head.next.val:
                head = head.next
            slow.next = head.next  # 连接到第一个不重复的元素
        else:
            slow = slow.next  # 不重复,慢指针前进
        head = head.next  # 快指针始终前进
    return dummy.next
# 示例调用代码
# 创建链表函数及打印函数假定已定义
算法分析
  • 时间复杂度:(O(n)),遍历整个链表。
  • 空间复杂度:(O(1)),只使用了有限的额外空间。

总结

不同算法的优劣势对比

特征 方法一:哨兵节点 方法二:递归法 方法三:一次遍历 方法四:哈希表 方法五:快慢指针
时间复杂度 (O(n)) (O(n)) (O(n)) (O(n)) (O(n))
空间复杂度 (O(1)) (O(n)) (O(1)) (O(n)) (O(1))
优势 简单直观,易于实现 自然处理,直观 避免使用额外空间 明确记录每个值的出现次数 避免额外空间,直观有效
劣势 基础方法 栈溢出风险,大量递归 稍微复杂 需要额外空间,复杂度增加 逻辑稍微复杂,需要细致处理

应用示例

去重应用:在需要去除重复数据的多种应用场景中,如邮件列表处理、社交网络服务中的好友推荐等。

数据清洗:在预处理数据时删除重复的数据项,确保数据的质量和分析的准确性。

内存优化:在资源受限的环境中,例如嵌入式系统或移动设备,优化内存使用,提高性能。

这些方法提供了多种处理链表去重的策略,适用于不同的场景和需求,选择合适的方法可以提高代码效率和可读性。


欢迎关注微信公众号 数据分析螺丝钉

目录
打赏
0
0
0
0
68
分享
相关文章
|
6月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
62 1
|
6月前
Leetcode第21题(合并两个有序链表)
这篇文章介绍了如何使用非递归和递归方法解决LeetCode第21题,即合并两个有序链表的问题。
92 0
Leetcode第21题(合并两个有序链表)
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
145 90
|
6月前
【LeetCode 27】347.前k个高频元素
【LeetCode 27】347.前k个高频元素
65 0
|
6月前
|
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
48 0
Leetcode第三十三题(搜索旋转排序数组)
|
6月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
59 0
LeetCode第二十四题(两两交换链表中的节点)
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
6月前
|
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
141 0
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等