题目链接

LeetCode

题目描述

给定一个可包含重复数字的序列 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

    解题思路

    方法一:回溯法

    本题目解到这里并没有满足「全排列不重复」 的要求,在上一题的递归函数中我们会生成大量重复的排列,因为对于第 idx 的位置,如果存在重复的数字 i,我们每次会将重复的数字都重新填上去并继续尝试导致最后答案的重复,因此我们需要处理这个情况。

要解决重复问题,我们只要设定一个规则,保证在填第 idx 个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」,即如下的判断条件

  1. if (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1]) {
  2. continue;
  3. }

这个判断条件保证了对于重复数的集合,一定是从左往右逐个填入的。

假设我们有 3 个重复数排完序后相邻,那么我们一定保证每次都是拿从左往右第一个未被填过的数字,即整个数组的状态其实是保证了 [未填入,未填入,未填入] 到 [填入,未填入,未填入],再到 [填入,填入,未填入],最后到 [填入,填入,填入] 的过程的,因此可以达到去重的目标。

加上 !vis[i - 1]来去重主要是通过限制一下两个相邻的重复数字的访问顺序
举个栗子,对于两个相同的数11,我们将其命名为1a1b, 1a表示第一个1,1b表示第二个1; 那么,不做去重的话,会有两种重复排列 1a1b, 1b1a, 我们只需要取其中任意一种排列; 为了达到这个目的,限制一下1a, 1b访问顺序即可。 比如我们只取1a1b那个排列的话,只有当visit nums[i-1]之后我们才去visit nums[i], 也就是如果!visited[i-1]的话则continue

  • 通过47. 全排列 II - 图1记录当前位置数字是否被加入
  • 通过47. 全排列 II - 图2记录当前数组
  • 首先将原生数组排序保证一样的数字在一起
    ```cpp class Solution { vector vis;

public: void backtrack(vector& nums, vector>& ans, int idx, vector& perm) { if (idx == nums.size()) { ans.emplace_back(perm); return; } // 每次从第一个开始遍历 for (int i = 0; i < (int)nums.size(); ++i) { // 如果当前数字已经被遍历或者当前数字是重复数字并且倩一个数字未被遍历 if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) { continue; } // 回溯 perm.emplace_back(nums[i]); vis[i] = 1; backtrack(nums, ans, idx + 1, perm); vis[i] = 0; perm.pop_back(); } }

  1. vector<vector<int>> permuteUnique(vector<int>& nums) {
  2. vector<vector<int>> ans;
  3. vector<int> perm;
  4. vis.resize(nums.size());
  5. sort(nums.begin(), nums.end());
  6. backtrack(nums, ans, 0, perm);
  7. return ans;
  8. }

}; ```

  • 时间复杂度 O(n*n!)
  • 空间复杂度 O(n)