LeetCode 47. Permutations II
2020-06-05 12:19:19 # leetcode # core problems

Problem

LeetCode 47. Permutations II

1. 题目简述

给出一列有重复的数,找出其所有的全排列。例如:

Input: [1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

2. 算法思路

back tracking回溯法的经典题目之一,LeetCode 46. Permutations的进阶版,问题在于我们怎么去应对这个重复数字的问题。

如果按照以往的思路,对于这种有重复数字的,我们首先需要对数组进行排序,然后才能更好地去对重复数字进行处理。

那么我们能否还像permutation
1的时候,交换然后再找全排列么?不行的,这种去重方式的核心思想在于后面不会再出现比当前更小的数字,这么说可能有点抽象,我们来看个例子。

我们给出一个排好序的数组[1, 1, 2, 2, 3],按照如上思路的话:

1. 从第1个数开始,分别和1、2、3、4、5进行替换,然后寻找其后面的全排列;

2. 为了不重复,我们每次(除了首次的自我替换)寻找替换的数字的时候,都要找到和当前不一样的数字为止,例如第1个1,和第2个1就不会进行替换,以此类推;

3. 那么问题会出在哪里呢?当我们需要替换第1个1和末尾的3时,数组变为[3, 1, 2, 2, 1],然后我们找出[1, 2, 2, 1]的全排列;

4. 按照我们的逻辑,1先和第1个2替换,然后跳过第2个2,然后判断到了第2个1,第2个1和它之前的数字2是不同的,因此我们再次替换,到这里我们就会发现出现了重复,明明1和1不应该再替换的,在这里却被再次替换,所以会多出很多重复。

因此我们去重的方式其实可以更简单一点,用hashset来判断是之前是否和同样的元素交换过就可以了,而且原始数组不需要排序也可以。

回溯法

注意这里去重的方式和subset 2以及combination sum
2的去重方式完全不同,具体原因在上面已经详细描述了,建议自己尝试写一下,这里把错误的代码放在文章末尾,大家有兴趣可以自己debug一下,看看哪里出了问题。

这里我们使用hashset来去重。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList();

if (nums.length == 0) {
return res;
}

getPermutations(nums, 0, res);

return res;
}

private void getPermutations(int[] nums, int start, List<List<Integer>> res) {
if (start == nums.length) {
List<Integer> solution = new ArrayList();
for (int num : nums) {
solution.add(num);
}
res.add(solution);
return;
}

Set<Integer> set = new HashSet();
for (int i = start; i < nums.length; i++) {
// 如果set中成功添加了num,则说明当前数字尚未和该数字交换过
if (set.add(nums[i])) {
swap(nums, start, i);
getPermutations(nums, start + 1, res);
swap(nums, start, i);
}
}
}

private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

错误版本的代码,警戒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList();

if (nums.length == 0) {
return res;
}

getPermutations(nums, 0, res);

// debug专用,找重复
// Set<List<Integer>> set = new HashSet();
// List<List<Integer>> newRes = new ArrayList();

// for (List<Integer> solution : res) {
// if (!set.add(solution)) {
// newRes.add(solution);
// }
// }

return res;
}

private void getPermutations(int[] nums, int start, List<List<Integer>> res) {
if (start == nums.length) {
List<Integer> solution = new ArrayList();
for (int num : nums) {
solution.add(num);
}
res.add(solution);
return;
}

for (int i = start; i < nums.length; i++) {
//这里的去重不行,有很多重复的case,这种写法对于[1,1,2,2,3]没问题,但是对于[0,0,1,1,2,2,3]有问题,自己可以尝试一下。
if (i > start && (nums[i] == nums[i - 1] || nums[start] == nums[i])) {
continue;
}
swap(nums, start, i);
getPermutations(nums, start + 1, res);
swap(nums, start, i);
}
}

private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}