LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll

简介: LeetCode题目 90:五种算法 回溯\迭代\位掩码\字典树\动态规划实现 子集ll

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

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

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

作者专栏每日更新:

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

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

python源码解读

程序员必备的数学知识与应用

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

题目描述

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

解集不能包含重复的子集。

输入格式
  • nums:一个整数数组,可能包含重复元素。
输出格式
  • 返回所有可能的子集列表。

示例

示例 1
输入: nums = [1,2,2]
输出: [[], [1], [1,2], [1,2,2], [2], [2,2]]

方法一:回溯法

解题步骤
  1. 排序:首先对数组进行排序,以方便处理重复元素。
  2. 递归构建:使用递归方法,针对每个元素选择“使用”或“不使用”,并跳过重复元素。
完整的规范代码
def subsetsWithDup(nums):
    """
    使用回溯法生成所有可能的子集,处理重复元素
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有可能的子集
    """
    nums.sort()  # 排序以方便处理重复元素
    results = []
    def backtrack(start, path):
        results.append(path[:])  # 添加当前子集
        for i in range(start, len(nums)):
            if i > start and nums[i] == nums[i-1]:
                continue  # 跳过重复元素
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    backtrack(0, [])
    return results
# 示例调用
print(subsetsWithDup([1,2,2]))  # 输出: [[], [1], [1,2], [1,2,2], [2], [2,2]]
算法分析
  • 时间复杂度:(O(2^n * n)),其中 (n) 是数组长度。每个元素有选择和不选择两种状态,因此时间复杂度为指数级。每次添加子集到结果中的时间复杂度为 (O(n))。
  • 空间复杂度:(O(n)),递归栈的深度最多为 (n)。

方法二:迭代法

解题步骤
  1. 迭代构建:从空集开始,每次迭代添加一个新元素到所有已存在的子集中,形成新的子集,并注意处理重复元素。
完整的规范代码
def subsetsWithDup(nums):
    """
    使用迭代法生成所有可能的子集,处理重复元素
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有可能的子集
    """
    nums.sort()
    results = [[]]
    start, end = 0, 0
    for i in range(len(nums)):
        start = 0
        # 如果当前数字和前一个数字相同,则只在上一轮新加入的子集基础上追加
        if i > 0 and nums[i] == nums[i-1]:
            start = end + 1
        end = len(results) - 1
        for j in range(start, len(results)):
            results.append(results[j] + [nums[i]])
    return results
# 示例调用
print(subsetsWithDup([1,2,2]))  # 输出: [[], [1], [1,2], [1,2,2], [2], [2,2]]
算法分析
  • 时间复杂度:(O(2^n * n)),每个元素可能会导致结果翻倍,需要遍历并复制现有的所有子集。
  • 空间复杂度:(O(2^n)),存储所有的子集。

方法三:位掩码法

解题步骤
  1. 位掩码生成:利用位掩码的方法生成所有可能的组合。
  2. 去重处理:对于生成的每个组合,检查是否是重复的,若不是则添加到结果集中。
完整的规范代码
def subsetsWithDup(nums):
    """
    使用位掩码法生成所有可能的子集,并处理重复元素
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有可能的子集
    """
    nums.sort()
    results = []
    seen = set()
    n = len(nums)
    for i in range(1 << n):  # 从 0 到 2^n - 1
        subset = []
        for j in range(n):
            if i & (1 << j):  # 检查第 j 位是否为 1
                subset.append(nums[j])
        # 转换成元组检查是否重复
        subset_tuple = tuple(subset)
        if subset_tuple not in seen:
            seen.add(subset_tuple)
            results.append(subset)
    return results
# 示例调用
print(subsetsWithDup([1,2,2]))  # 输出: [[], [1], [1,2], [1,2,2], [2], [2,2]]
算法分析
  • 时间复杂度:(O(2^n * n)),位掩码方法生成所有可能的子集,并对每个子集进行处理。
  • 空间复杂度:(O(2^n)),用于存储结果和用于查重的集合。

方法四:字典树(Trie)技术

解题步骤
  1. 构建字典树:为数组中的每个元素构建一个字典树,以存储所有可能的子集组合。
  2. 遍历字典树:遍历字典树以生成所有唯一的子集。
完整的规范代码

这个方法在实际应用中比较复杂,通常不推荐用于这种类型的问题,因为它过于复杂,适合处理更具体的、需要快速查找和插入的场景。因此,这里不提供具体代码实现,而是保持理论上的探讨。

方法五:动态规划

解题步骤
  1. 初始化动态表:以空集开始,逐步添加元素。
  2. 处理重复元素:对于重复元素,仅在上一次新增的子集中添加,防止产生重复的子集。
完整的规范代码
def subsetsWithDup(nums):
    """
    动态规划法生成所有可能的子集,并处理重复元素
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有可能的子集
    """
    nums.sort()
    results = [[]]
    last_count = 0
    for i in range(len(nums)):
        start = last_count if i > 0 and nums[i] == nums[i-1] else 0
        last_count = len(results)
        results += [r + [nums[i]] for r in results[start:]]
    return results
# 示例调用
print(subsetsWithDup([1,2,2]))  # 输出: [[], [1], [1,2], [1,2,2], [2], [2,2]]
算法分析
  • 时间复杂度:(O(2^n)),每次迭代都会根据当前结果数量添加新的子集。
  • 空间复杂度:(O(2^n)),存储所有生成的子集。

不同算法的优劣势对比

特征 方法一:回溯法 方法二:迭代法 方法三:位掩码法 方法四:字典树技术 方法五:动态规划
时间复杂度 (O(2^n * n)) (O(2^n * n)) (O(2^n * n)) 高于 (O(2^n)) (O(2^n))
空间复杂度 (O(n)) (O(2^n)) (O(2^n)) 可能非常高 (O(2^n))
优势 直观,易于理解 简单易实现 高效,直接 高效查找和插入 高效,易于实现
劣势 可能栈溢出 结果存储消耗大 需要额外处理重复 过于复杂 需要处理重复元素

应用示例

这些方法在处理涉及到组合生成的问题时非常有用,例如在权限管理系统中生成角色的权限组合、在统计学中用于数据分析的各种组合情况生成等。


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

相关文章
|
5月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
677 1
|
10月前
|
机器学习/深度学习 算法 Go
【LeetCode 热题100】139:单词拆分(动态规划全解析+细节陷阱)(Go语言版)
本题是 LeetCode 热题 139:单词拆分(Word Break),需判断字符串 `s` 是否能由字典 `wordDict` 中的单词拼接而成。通过动态规划(DP)或记忆化搜索解决。DP 中定义布尔数组 `dp[i]` 表示前 `i` 个字符是否可拆分,状态转移方程为:若存在 `j` 使 `dp[j]=true` 且 `s[j:i]` 在字典中,则 `dp[i]=true`。初始条件 `dp[0]=true`。代码实现中用哈希集合优化查找效率。记忆化搜索则从起始位置递归尝试所有切割点。两种方法各有利弊,DP 更适合面试场景。思考扩展包括输出所有拆分方式及使用 Trie 优化大字典查找。
332 6
|
12月前
|
存储 算法 Java
算法系列之动态规划
动态规划(Dynamic Programming,简称DP)是一种用于解决复杂问题的算法设计技术。它通过将问题分解为更小的子问题,并存储这些子问题的解来避免重复计算,从而提高算法的效率。
466 4
算法系列之动态规划
|
算法 安全 调度
【动态规划篇】穿越算法迷雾:约瑟夫环问题的奇幻密码
【动态规划篇】穿越算法迷雾:约瑟夫环问题的奇幻密码
|
机器学习/深度学习 算法 测试技术
【动态规划篇】01 背包的逆袭:如何用算法装满你的 “财富背包”
【动态规划篇】01 背包的逆袭:如何用算法装满你的 “财富背包”
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
344 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
198 6
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
442 2
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
347 3
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
411 1

热门文章

最新文章