47. 全排列 II

186 阅读3分钟

【题目】

给定一个可包含重复数字的序列 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 <= nums.length <= 8
  • -10 <= nums[i] <= 10

【题目解析】

解题思路

这个问题的解决方案依赖于回溯算法,结合剪枝技术来避免重复排列的生成。

  • 排序:首先对输入数组进行排序,使得相同的元素相邻,便于后续的剪枝操作。
  • 回溯搜索:通过递归函数遍历每个可能的选择,构造排列。
  • 剪枝条件:利用排序后的数组特性,通过判断相邻元素是否相同以及使用状态,来决定是否跳过当前元素,从而避免重复排列的生成。
  • 状态标记:使用一个布尔数组来标记哪些元素已被选取,确保每个元素在每个排列中只使用一次。
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def backtrack(path, used):
            # 如果路径长度等于nums的长度,说明找到了一种排列,加入结果列表
            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

执行效率

image.png

【总结】

适用问题类型

该方法适用于解决需要从一组可能包含重复元素的数字中生成所有唯一排列的问题。它特别适合于那些排列组合、决策树构建及其它需要考虑元素顺序和组合唯一性的场景。

解决算法:回溯算法 + 剪枝

  • 算法描述:回溯算法通过递归模拟所有可能的决策路径,剪枝操作则在递归过程中动态排除那些显然不会得到最终解的路径,从而减少搜索空间,提高效率。

  • 算法特点

    • 高灵活性:通过调整剪枝条件和递归结构,可以适用于广泛的问题。
    • 高效率:剪枝显著减少无效的递归调用,尤其是在处理大规模数据时。

时间复杂度与空间复杂度

  • 时间复杂度:O(n*n!),其中n是数组长度。最坏情况下,需要遍历n!种排列情况,每种排列情况需要O(n)的时间复制到结果数组中。
  • 空间复杂度:O(n),主要空间开销来源于递归调用栈以及用于构造排列的临时数组。递归深度最多为n,因此空间复杂度为O(n)。

实践意义

回溯算法加剪枝在许多领域都有广泛应用,特别是在算法设计、数据分析、人工智能等领域,用于解决组合优化问题、搜索问题等。全排列 II 问题的解决方案不仅强化了对回溯算法及剪枝技术的理解,也提供了一种通用的解决框架,可以应用于解决其他需要去重的组合问题,如组合总和、子集构造等,显示了算法设计中的通用性与实用性。此外,熟悉这类算法对于提高编程能力、解决实际问题具有重要的价值。

题目链接

全排列 II