1
- ## 题目地址
2
- https://leetcode-cn.com/problems/permutations-ii/
3
1
4
- ## 思路
5
-
6
- 这道题目和46.全排列的区别在与** 给定一个可包含重复数字的序列** ,要返回** 所有不重复的全排列** 。
2
+ > 排列也要去重了
3
+ > 通知:很多录友都反馈之前看「算法汇总」的目录要一直往下拉,很麻烦,这次Carl将所有历史文章汇总到一篇文章中,有一个整体的目录,方便录友们从前面系列开始卡了,依然在公众号左下角[ 「算法汇总」] ( https://mp.weixin.qq.com/s/weyitJcVHBgFtSc19cbPdw ) ,这里会持续更新,大家快去瞅瞅哈
7
4
8
- 这里就涉及到去重了。
5
+ # 47.全排列 II
9
6
7
+ 题目链接:https://leetcode-cn.com/problems/permutations-ii/
10
8
11
- 要注意 ** 全排列是要取树的子节点的,如果是子集问题,就取树上的所有节点。 **
9
+ 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
12
10
13
- 很多同学在去重上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
11
+ 示例 1:
12
+ 输入:nums = [ 1,1,2]
13
+ 输出:
14
+ [[ 1,1,2] ,
15
+ [ 1,2,1] ,
16
+ [ 2,1,1]]
14
17
15
- 这个去重为什么很难理解呢,** 所谓去重,其实就是使用过的元素不能重复选取。** 这么一说好像很简单!
18
+ 示例 2:
19
+ 输入:nums = [ 1,2,3]
20
+ 输出:[[ 1,2,3] ,[ 1,3,2] ,[ 2,1,3] ,[ 2,3,1] ,[ 3,1,2] ,[ 3,2,1]]
16
21
22
+ 提示:
23
+ * 1 <= nums.length <= 8
24
+ * -10 <= nums[ i] <= 10
17
25
18
- 但是什么又是“使用过”,我们把排列问题抽象为树形结构之后, ** “使用过”在这个树形结构上是有两个维度的 ** ,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。
26
+ ## 思路
19
27
28
+ 这道题目和[ 回溯算法:排列问题!] ( https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw ) 的区别在与** 给定一个可包含重复数字的序列** ,要返回** 所有不重复的全排列** 。
20
29
21
- ** 没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。 **
30
+ 这里又涉及到去重了。
22
31
23
- 那么排列问题,既可以在 同一树层上的“使用过”来去重,也可以在同一树枝上的“使用过”来去重!
32
+ 在 [ 回溯算法:求组合总和(三) ] ( https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ ) 、 [ 回溯算法:求子集问题(二) ] ( https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ ) 我们分别详细讲解了组合问题和子集问题如何去重。
24
33
25
- 理解这一本质,很多疑点就迎刃而解了 。
34
+ 那么排列问题其实也是一样的套路 。
26
35
27
- ** 还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。 **
36
+ ** 还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了** 。
28
37
29
- 首先把示例中的 [ 1,1,2] (为了方便举例,已经排序), 抽象为一棵树,然后在同一树层上对nums [ i-1 ] 使用过的话,进行去重如图 :
38
+ 我以示例中的 [ 1,1,2] 为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图 :
30
39
31
40
<img src =' ../pics/47.全排列II1.png ' width =600 > </img ></div >
32
41
33
42
图中我们对同一树层,前一位(也就是nums[ i-1] )如果使用过,那么就进行去重。
34
43
35
- 代码如下:
44
+ ** 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果** 。
45
+
46
+ 在[ 回溯算法:排列问题!] ( https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw ) 中已经详解讲解了排列问题的写法,在[ 回溯算法:求组合总和(三)] ( https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ ) 、[ 回溯算法:求子集问题(二)] ( https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ ) 中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下:
36
47
37
48
## C++代码
38
49
39
50
```
40
51
class Solution {
41
52
private:
42
53
vector<vector<int>> result;
43
- void backtracking (vector<int>& nums, vector<int>& vec, vector<bool>& used) {
54
+ vector<int> path;
55
+ void backtracking (vector<int>& nums, vector<bool>& used) {
44
56
// 此时说明找到了一组
45
- if (vec .size() == nums.size()) {
46
- result.push_back(vec );
57
+ if (path .size() == nums.size()) {
58
+ result.push_back(path );
47
59
return;
48
60
}
49
-
50
61
for (int i = 0; i < nums.size(); i++) {
51
- // 这里理解used[i - 1]非常重要
52
- // used[i - 1] == true,说明同一树支nums[i - 1]使用过
62
+ // used[i - 1] == true,说明同一树支nums[i - 1]使用过
53
63
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
54
64
// 如果同一树层nums[i - 1]使用过则直接跳过
55
- if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
65
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
56
66
continue;
57
67
}
58
68
if (used[i] == false) {
59
69
used[i] = true;
60
- vec .push_back(nums[i]);
61
- backtracking(nums, vec, used);
62
- vec .pop_back();
70
+ path .push_back(nums[i]);
71
+ backtracking(nums, used);
72
+ path .pop_back();
63
73
used[i] = false;
64
74
}
65
75
}
66
76
}
67
-
68
77
public:
69
78
vector<vector<int>> permuteUnique(vector<int>& nums) {
70
- sort(nums.begin(), nums.end());
79
+ result.clear();
80
+ path.clear();
81
+ sort(nums.begin(), nums.end()); // 排序
71
82
vector<bool> used(nums.size(), false);
72
- vector<int> vec;
73
83
backtracking(nums, vec, used);
74
84
return result;
75
-
76
85
}
77
86
};
87
+
78
88
```
79
89
80
90
## 拓展
@@ -87,14 +97,14 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
87
97
}
88
98
```
89
99
90
- 可是如果把 ` used[i - 1] == true ` 也是正确的,去重代码如下:
100
+ ** 如果改成 ` used[i - 1] == true ` , 也是正确的! ** ,去重代码如下:
91
101
```
92
102
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
93
103
continue;
94
104
}
95
105
```
96
106
97
- 这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用` used[i - 1] == false ` ,如果要对树枝前一位去重用用 ` used[i - 1] == true ` 。
107
+ 这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用` used[i - 1] == false ` ,如果要对树枝前一位去重用 ` used[i - 1] == true ` 。
98
108
99
109
** 对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!**
100
110
@@ -110,5 +120,28 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
110
120
111
121
<img src =' ../pics/47.全排列II3.png ' width =600 > </img ></div >
112
122
113
- 大家应该很清晰的看到,树层上去重非常彻底,效率很高,树枝上去重虽然最后可能得到答案,但是多做了很多无用搜索。
123
+ 大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
124
+
125
+ # 总结
126
+
127
+ 这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写:
128
+ ```
129
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
130
+ continue;
131
+ }
132
+ ```
133
+ 和这么写:
134
+ ```
135
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
136
+ continue;
137
+ }
138
+ ```
139
+
140
+ 都是可以的,这也是很多同学做这道题目困惑的地方,知道` used[i - 1] == false ` 也行而` used[i - 1] == true ` 也行,但是就想不明白为啥。
141
+
142
+ 所以我通过举[ 1,1,1] 的例子,把这两个去重的逻辑分别抽象成树形结构,大家可以一目了然:为什么两种写法都可以以及哪一种效率更高!
143
+
144
+ 是不是豁然开朗了!!
145
+
146
+ 就酱,很多录友表示和「代码随想录」相见恨晚,那么大家帮忙多多宣传,让更多的同学知道这里,感谢啦!
114
147
0 commit comments