Python5种算法回溯+剪枝、字典序、递归交换、计数回溯、迭代法 实现全排列ll【力扣题47】

简介: Python5种算法回溯+剪枝、字典序、递归交换、计数回溯、迭代法 实现全排列ll【力扣题47】

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

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

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

作者专栏每日更新:

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

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

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

题目描述

给定一个可能包含重复数字的数组 nums,返回所有可能的唯一全排列。你可以以任意顺序返回答案。

LeetCode 题目 47 “全排列 II” 与题目 46 “全排列” 的主要区别在于输入数组中的元素是否可以包含重复数字。这个差异对解题策略和算法实现有重大影响。

主要区别
  1. 输入元素的不同
  • 题目 46:输入的数组中的数字不重复,这意味着每个数字在每个排列中只会出现一次。
  • 题目 47:输入的数组中的数字可能重复,这意味着需要避免生成重复的排列,排列结果中可能会多次出现同一数字。
  1. 解题难度
  • 因为题目 47 允许输入数组中包含重复数字,仅使用简单的回溯算法可能会生成重复的排列结果,因此需要加入额外的机制(如排序和剪枝)来避免重复排列的生成。
  • 而题目 46 由于保证输入数组中的数字不重复,可以直接进行回溯,不需要处理排列结果的去重问题。
输入格式
  • nums:一个整数数组。
输出格式
  • 返回一个列表,包含所有不重复的全排列。

示例

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

方法一:回溯算法 + 剪枝

解题步骤
  1. 排序:首先对数组进行排序,以方便后续剪枝操作。
  2. 回溯函数定义:定义一个回溯函数,使用当前序列 path 和动态的选择列表 used 来生成排列。
  3. 递归终止条件:如果 path 的长度等于 nums 的长度,则添加到结果列表。
  4. 循环选择:遍历 nums 中的每个数字,根据 used 状态决定是否可以选择当前数字。
  5. 剪枝条件:如果当前数字和前一个数字相同,且前一个数字未被使用,则跳过当前数字。
完整的规范代码
def permuteUnique(nums):
    """
    使用回溯算法加剪枝生成数组的所有不重复全排列
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有不重复的全排列
    """
    def backtrack(path, used):
        if len(path) == len(nums):
            res.append(path[:])
            return
        for i in range(len(nums)):
            if used[i] or (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]):
                continue
            used[i] = True
            path.append(nums[i])
            backtrack(path, used)
            path.pop()
            used[i] = False
    nums.sort()  # 排序以方便剪枝
    res = []
    backtrack([], [False] * len(nums))
    return res
# 示例调用
print(permuteUnique([1, 1, 2]))  # 输出: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
算法分析
  • 时间复杂度:(O(n!)),尽管使用了剪枝,但在最坏情况下,仍需尝试所有排列。
  • 空间复杂度:(O(n)),递归深度为 (n),使用了额外的空间存储当前排列和状态标记。

方法二:字典序排列法

解题步骤
  1. 排序:与方法一相同,首先排序数组。
  2. 查找下一个排列:实现一个函数,每次调用生成数组的下一个字典序排列。
  3. 迭代生成:从初始排序开始,持续生成下一个排列,直到回到初始排序。
完整的规范代码
def permuteUnique(nums):
    """
    使用字典序排列法生成数组的所有不重复全排列
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有不重复的全排列
    """
    def next_permutation(nums):
        i = len(nums) - 2
        while i >= 0 and nums[i] >= nums[i + 1]:
            i -= 1
        if i == -1:
            nums.reverse()
            return False
        j = len(nums) - 1
        while nums[j] <= nums[i]:
            j -= 1
        nums[i], nums[j] = nums[j], nums[i]
        nums[i + 1:] = reversed(nums[i + 1:])
        return True
    nums.sort()
    res = [nums[:]]
    while next_permutation(nums):
        res.append(nums[:])
    return res
# 示例调用
print(permuteUnique([1, 1, 2]))  # 输出: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
算法分析
  • 时间复杂度:(O(n! ✖️ n)),生成每个排列需要 (O(n)) 时间用于找下一个排列。
  • 空间复杂度:(O(1)),除了输出数组外,空间使用是常数。

方法三:递归交换法

解题步骤
  1. 递归函数定义:定义一个递归函数,使用当前索引 start 来处理排列。
  2. 递归终止条件:如果 start 等于数组长度,记录当前排列。
  3. 递归交换:从 start 开始,将每个元素交换到 start 位置,然后递归处理 start + 1。注意剪枝重复元素。
完整的规范代码
def permuteUnique(nums):
    """
    使用递归交换方法生成数组的所有不重复全排列
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有不重复的全排列
    """
    def backtrack(start):
        if start == len(nums):
            res.append(nums[:])
            return
        seen = set()
        for i in range(start, len(nums)):
            if nums[i] in seen:
                continue
            seen.add(nums[i])
            nums[start], nums[i] = nums[i], nums[start]
            backtrack(start + 1)
            nums[start], nums[i] = nums[i], nums[start]
    nums.sort()  # 排序以方便剪枝
    res = []
    backtrack(0)
    return res
# 示例调用
print(permuteUnique([1, 1, 2]))  # 输出: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
算法分析
  • 时间复杂度:(O(n!)),通过剪枝减少了不必要的递归调用。
  • 空间复杂度:(O(n)),递归深度为 (n),使用了额外的空间存储状态。

方法四:基于计数的回溯法

解题步骤
  1. 计数统计:使用字典统计每个数字的出现次数。
  2. 回溯生成:基于数字的计数来生成排列,当某个数字的计数用尽时,不再考虑该数字。
完整的规范代码
def permuteUnique(nums):
    """
    使用基于计数的回溯法生成数组的所有不重复全排列
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有不重复的全排列
    """
    def backtrack(comb, counter):
        if len(comb) == len(nums):
            res.append(comb[:])
            return
        for num in counter:
            if counter[num] > 0:
                comb.append(num)
                counter[num] -= 1
                backtrack(comb, counter)
                comb.pop()
                counter[num] += 1
    counter = {}
    for num in nums:
        if num in counter:
            counter[num] += 1
        else:
            counter[num] = 1
    res = []
    backtrack([], counter)
    return res
# 示例调用
print(permuteUnique([1, 1, 2]))  # 输出: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
算法分析
  • 时间复杂度:(O(n!)),尽管使用了计数来优化,但在最坏情况下仍需尝试所有排列。
  • 空间复杂度:(O(n)),递归深度和计数存储共同决定了空间复杂度。

方法五:迭代法

解题步骤
  1. 迭代构建:从一个空排列开始,逐步在排列中插入新的数字,同时保证插入后的排列仍然是有效的。
  2. 避免重复:使用集合来避免在同一位置插入重复数字。
完整的规范代码
def permuteUnique(nums):
    """
    使用迭代法生成数组的所有不重复全排列
    :param nums: List[int], 输入的整数数组
    :return: List[List[int]], 所有不重复的全排列
    """
    perms = [[]]
    for num in nums:
        new_perms = []
        for perm in perms:
            for i in range(len(perm) + 1):
                new_perms.append(perm[:i] + [num] + perm[i:])
                if i < len(perm) and perm[i] == num:
                    break
        perms = new_perms
    return perms
# 示例调用
print(permuteUnique([1, 1, 2]))  # 输出: [[1, 1, 2], [1, 2, 1], [2, 1, 1]]
算法分析
  • 时间复杂度:(O(n! ✖️ n)),每次插入操作均涉及到复制和插入操作,这在元素数量较大时尤为显著。
  • 空间复杂度:(O(n! ✖️ n)),存储所有的排列需要的空间。

总结

为了直观地比较解决LeetCode题目47 "全排列 II"的五种方法,下面的表格将展示它们在不同技术维度的性能和特点,包括时间复杂度、空间复杂度以及各自的优势和劣势。

特征 方法一:回溯 + 剪枝 方法二:字典序算法 方法三:递归交换法 方法四:基于计数回溯 方法五:迭代法
时间复杂度 (O(n!)) (O(n! ✖️ n)) (O(n!)) (O(n!)) (O(n! ✖️ n))
空间复杂度 (O(n)) (O(1)) (O(n)) (O(n)) (O(n! ✖️ n))
优势 - 有效避免重复
- 直观易懂
- 生成排列效率高
- 无需递归
- 简单易实现
- 直接操作数组
- 处理重复元素高效
- 直观实现
- 不使用递归
- 易于理解和实现
劣势 - 需要额外的剪枝逻辑 - 实现复杂
- 不直观
- 时间复杂度较高
- 重复排列处理复杂
- 空间复杂度相对较高 - 空间占用较大
- 效率可能较低
适用场景 - 数组较小时
- 需要去重排列
- 大数据处理
- 需字典顺序排列
- 喜欢直观操作
- 适合小规模数据
- 数组中重复元素多时 - 非递归需求
- 理解迭代处理
综合分析

方法一(回溯 + 剪枝)

  • 通过排序和剪枝避免重复,是解决包含重复元素的全排列问题的标准方法。虽然时间复杂度为 (O(n!)),但剪枝操作大大减少了无效的递归调用。

方法二(字典序算法)

  • 通过迭代计算下一个字典序的排列,该方法适用于需要按顺序生成排列的场景。其主要缺点是实现相对复杂,可能不太直观,尤其是对初学者。

方法三(递归交换法)

  • 直接在数组上进行操作,通过递归交换生成所有排列。该方法简单直观,但在数组中存在重复数字时需要额外的逻辑来避免生成重复排列。

方法四(基于计数的回溯法)

  • 利用计数来控制元素使用次数,适合处理含有多重复元素的数组。该方法可以有效地减少重复排列的生成,但空间复杂度略高,因为需要额外的计数存储。

方法五(迭代法)

  • 使用迭代而非递归来生成排列,适用于希望避免递归或栈溢出的情况。尽管易于实现,但其在处理大规模数据时可能效率不高,特别是空间占用较大。

在选择合适的方法时,应考虑实际的需求和问题规模。例如,对于需要


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

相关文章
|
17天前
|
存储 算法 量子技术
解锁文档管理系统高效检索奥秘:Python 哈希表算法探究
在数字化时代,文档管理系统犹如知识宝库,支撑各行各业高效运转。哈希表作为核心数据结构,通过哈希函数将数据映射为固定长度的哈希值,实现快速查找与定位。本文聚焦哈希表在文档管理中的应用,以Python代码示例展示其高效检索特性,并探讨哈希冲突解决策略,助力构建智能化文档管理系统。
|
20天前
|
算法 安全 网络安全
基于 Python 的布隆过滤器算法在内网行为管理中的应用探究
在复杂多变的网络环境中,内网行为管理至关重要。本文介绍布隆过滤器(Bloom Filter),一种高效的空间节省型概率数据结构,用于判断元素是否存在于集合中。通过多个哈希函数映射到位数组,实现快速访问控制。Python代码示例展示了如何构建和使用布隆过滤器,有效提升企业内网安全性和资源管理效率。
49 9
|
21天前
|
算法 Serverless 数据处理
从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
47 12
|
27天前
|
监控 算法 安全
内网桌面监控软件深度解析:基于 Python 实现的 K-Means 算法研究
内网桌面监控软件通过实时监测员工操作,保障企业信息安全并提升效率。本文深入探讨K-Means聚类算法在该软件中的应用,解析其原理与实现。K-Means通过迭代更新簇中心,将数据划分为K个簇类,适用于行为分析、异常检测、资源优化及安全威胁识别等场景。文中提供了Python代码示例,展示如何实现K-Means算法,并模拟内网监控数据进行聚类分析。
39 10
|
2月前
|
存储 算法 安全
控制局域网上网软件之 Python 字典树算法解析
控制局域网上网软件在现代网络管理中至关重要,用于控制设备的上网行为和访问权限。本文聚焦于字典树(Trie Tree)算法的应用,详细阐述其原理、优势及实现。通过字典树,软件能高效进行关键词匹配和过滤,提升系统性能。文中还提供了Python代码示例,展示了字典树在网址过滤和关键词屏蔽中的具体应用,为局域网的安全和管理提供有力支持。
59 17
|
2月前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
198 5
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
3月前
|
算法 网络协议 Python
探秘Win11共享文件夹之Python网络通信算法实现
本文探讨了Win11共享文件夹背后的网络通信算法,重点介绍基于TCP的文件传输机制,并提供Python代码示例。Win11共享文件夹利用SMB协议实现局域网内的文件共享,通过TCP协议确保文件传输的完整性和可靠性。服务器端监听客户端连接请求,接收文件请求并分块发送文件内容;客户端则连接服务器、接收数据并保存为本地文件。文中通过Python代码详细展示了这一过程,帮助读者理解并优化文件共享系统。
|
4天前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建 RESTful API
本文深入探讨了使用 Python 构建 RESTful API 的方法,涵盖 Flask、Django REST Framework 和 FastAPI 三个主流框架。通过实战项目示例,详细讲解了如何处理 GET、POST 请求,并返回相应数据。学习这些技术将帮助你掌握构建高效、可靠的 Web API。
|
4天前
|
机器学习/深度学习 设计模式 测试技术
Python 高级编程与实战:构建自动化测试框架
本文深入探讨了Python中的自动化测试框架,包括unittest、pytest和nose2,并通过实战项目帮助读者掌握这些技术。文中详细介绍了各框架的基本用法和示例代码,助力开发者快速验证代码正确性,减少手动测试工作量。学习资源推荐包括Python官方文档及Real Python等网站。
|
5天前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建微服务架构
本文深入探讨了 Python 中的微服务架构,介绍了 Flask、FastAPI 和 Nameko 三个常用框架,并通过实战项目帮助读者掌握这些技术。每个框架都提供了构建微服务的示例代码,包括简单的 API 接口实现。通过学习本文,读者将能够使用 Python 构建高效、独立的微服务。

热门文章

最新文章

推荐镜像

更多