题目

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这个题和47. 全排列 II - 图1题不同的是输入数组可以包含重复数组,如果仍按47. 全排列 II - 图2题解法,输出会有重复的排列,因此需要想办法去掉。

例如输入数组为47. 全排列 II - 图3,我们用47. 全排列 II - 图447. 全排列 II - 图5来区分两个相同的47. 全排列 II - 图6,树形图如下。划红线的是重复的排列,我们需要将其去掉,对应应该在红叉处剪枝。

image.png

重点就是如何剪枝呢?第一步容易想到的就是我们先对输入数组进行排序,将相同的元素放在一起,这样好判断。另外,我们可以分析,在47. 全排列 II - 图8处为什么会产生和47. 全排列 II - 图9处相同的分支呢?这是因为排序后的47. 全排列 II - 图10和前面的47. 全排列 II - 图11在数值上相等,尽管最后47. 全排列 II - 图1247. 全排列 II - 图13的位置不同,但两者数值相同,表示的排列也相同,因此产生了重复。

进一步,我们发现,正是因为前面的47. 全排列 II - 图1447. 全排列 II - 图15处可以被选择,才会在下一层选择47. 全排列 II - 图16后造成重复。同样,47. 全排列 II - 图17处也是因为47. 全排列 II - 图18前面的47. 全排列 II - 图19被撤销过进而可以选择,所以再次选择47. 全排列 II - 图20时会重复。而47. 全排列 II - 图21处则不同,此处47. 全排列 II - 图22已经被选择,那么便不会产生重复。

综上,我们去重的条件就是当前选择的数等于排序后数组前一个数,并且前一个数未被选择。

代码

  1. class Solution {
  2. List<List<Integer>> ans;
  3. public List<List<Integer>> permuteUnique(int[] nums) {
  4. // 对输入数组排序,方便去重剪枝
  5. Arrays.sort(nums);
  6. ans = new ArrayList<>();
  7. int n = nums.length;
  8. boolean[] used = new boolean[n];
  9. Deque<Integer> path = new ArrayDeque<>();
  10. dfs(n, nums, path, used);
  11. return ans;
  12. }
  13. private void dfs(int n, int[] nums, Deque<Integer> path, boolean[] used) {
  14. if (path.size() == n) {
  15. ans.add(new ArrayList<>(path));
  16. return;
  17. }
  18. for (int i = 0; i < n; i++) {
  19. // 和46题不同的是,这里添加了一种跳过的情况:nums[i] == nums[i - 1] && !used[i - 1] 即当前数等于前一个数,并且前一个数未被选择
  20. if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
  21. continue;
  22. }
  23. used[i] = true;
  24. path.offerLast(nums[i]);
  25. dfs(n, nums, path, used);
  26. used[i] = false;
  27. path.pollLast();
  28. }
  29. }
  30. }