在问 · 算法题解

47. 全排列 II

中等回溯排序 + 回溯(used 剪枝重复)力扣原题 ↗

题目描述

输入一个整数数组 nums,其中可能有相同的数。

把全部元素各用一次排成一个序列,就是一个排列。返回所有互不相同的排列:由于交换两个相同的数而得到的同一序列只能出现一次。返回顺序不限。

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

解释:两个 1 互换不产生新序列,只有 3 种不同的排列。

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

解释:没有重复元素,共 6 种排列。

数据范围

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

解题思路:排序 + 回溯(used 剪枝重复)

先排序让相同的数相邻,再用 used 数组逐位填排列。关键是去重规则:如果 nums[i] 与 nums[i-1] 相同且 nums[i-1] 还没被用,就跳过 nums[i]。这等于规定一组相同的数必须按下标从小到大的顺序使用,于是每种数值排列只会被生成一次。

为什么正确

任何时刻,每组相同数字中被使用的都是下标最小的若干个,且按下标顺序进入 path。

复杂度分析

时间 O(n · n!),空间 O(n)(不计输出)。

易错点

忘记先排序,或者把条件写成 used[i-1](反向)却不理解其含义;未排序时相同的数不相邻,去重条件完全失效。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res, path = [], []
        used = [False] * len(nums)

        def dfs():
            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])
                dfs()
                path.pop()
                used[i] = False

        dfs()
        return res

载入编辑器,自己跑一遍 →

其他解法

回溯法通过“相同元素必须按顺序使用”的剪枝来去重,框架可推广到带约束的搜索;下一个排列法按字典序逐个枚举不同排列,去重由严格比较自然保证,额外空间为常数。

解法 2:反复求下一个排列 时间 O(n·n!),空间 O(1)

先把数组升序排列,这是字典序最小的排列。之后反复执行第 31 题“下一个排列”:从右往左找第一个 nums[i] < nums[i+1] 的位置 i;再从右往左找第一个大于 nums[i] 的 nums[j],交换两者;最后把 i 右侧反转成升序。每得到一个新排列就记录下来,直到找不到 i(已是降序,即最大排列)为止。比较时用严格大小关系,所以相等元素不会生成重复排列。

不需要递归和 used 数组,除结果外只占常数空间,并且天然按字典序、自动去重。缺点是要先掌握“下一个排列”的推导,而且无法像回溯那样在中途加入剪枝条件。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        n = len(nums)
        res = [nums[:]]
        while True:
            i = n - 2
            while i >= 0 and nums[i] >= nums[i + 1]:
                i -= 1
            if i < 0:
                break
            j = n - 1
            while nums[j] <= nums[i]:
                j -= 1
            nums[i], nums[j] = nums[j], nums[i]
            nums[i + 1:] = nums[i + 1:][::-1]
            res.append(nums[:])
        return res

回溯 专题的其他题目

查看 回溯 全部题目 →