1- ## 题目地址
2- https://leetcode-cn.com/problems/permutations-ii/
31
4- ## 思路
5-
6- 这道题目和46.全排列的区别在与** 给定一个可包含重复数字的序列** ,要返回** 所有不重复的全排列** 。
2+ > 排列也要去重了
3+ > 通知:很多录友都反馈之前看「算法汇总」的目录要一直往下拉,很麻烦,这次Carl将所有历史文章汇总到一篇文章中,有一个整体的目录,方便录友们从前面系列开始卡了,依然在公众号左下角[ 「算法汇总」] ( https://mp.weixin.qq.com/s/weyitJcVHBgFtSc19cbPdw ) ,这里会持续更新,大家快去瞅瞅哈
74
8- 这里就涉及到去重了。
5+ # 47.全排列 II
96
7+ 题目链接:https://leetcode-cn.com/problems/permutations-ii/
108
11- 要注意 ** 全排列是要取树的子节点的,如果是子集问题,就取树上的所有节点。 **
9+ 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
1210
13- 很多同学在去重上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
11+ 示例 1:
12+ 输入:nums = [ 1,1,2]
13+ 输出:
14+ [[ 1,1,2] ,
15+ [ 1,2,1] ,
16+ [ 2,1,1]]
1417
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]]
1621
22+ 提示:
23+ * 1 <= nums.length <= 8
24+ * -10 <= nums[ i] <= 10
1725
18- 但是什么又是“使用过”,我们把排列问题抽象为树形结构之后, ** “使用过”在这个树形结构上是有两个维度的 ** ,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。
26+ ## 思路
1927
28+ 这道题目和[ 回溯算法:排列问题!] ( https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw ) 的区别在与** 给定一个可包含重复数字的序列** ,要返回** 所有不重复的全排列** 。
2029
21- ** 没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。 **
30+ 这里又涉及到去重了。
2231
23- 那么排列问题,既可以在 同一树层上的“使用过”来去重,也可以在同一树枝上的“使用过”来去重!
32+ 在 [ 回溯算法:求组合总和(三) ] ( https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ ) 、 [ 回溯算法:求子集问题(二) ] ( https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ ) 我们分别详细讲解了组合问题和子集问题如何去重。
2433
25- 理解这一本质,很多疑点就迎刃而解了 。
34+ 那么排列问题其实也是一样的套路 。
2635
27- ** 还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。 **
36+ ** 还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了** 。
2837
29- 首先把示例中的 [ 1,1,2] (为了方便举例,已经排序), 抽象为一棵树,然后在同一树层上对nums [ i-1 ] 使用过的话,进行去重如图 :
38+ 我以示例中的 [ 1,1,2] 为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图 :
3039
3140<img src =' ../pics/47.全排列II1.png ' width =600 > </img ></div >
3241
3342图中我们对同一树层,前一位(也就是nums[ i-1] )如果使用过,那么就进行去重。
3443
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 ) 中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下:
3647
3748## C++代码
3849
3950```
4051class Solution {
4152private:
4253 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) {
4456 // 此时说明找到了一组
45- if (vec .size() == nums.size()) {
46- result.push_back(vec );
57+ if (path .size() == nums.size()) {
58+ result.push_back(path );
4759 return;
4860 }
49-
5061 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]使用过
5363 // used[i - 1] == false,说明同一树层nums[i - 1]使用过
5464 // 如果同一树层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) {
5666 continue;
5767 }
5868 if (used[i] == false) {
5969 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();
6373 used[i] = false;
6474 }
6575 }
6676 }
67-
6877public:
6978 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()); // 排序
7182 vector<bool> used(nums.size(), false);
72- vector<int> vec;
7383 backtracking(nums, vec, used);
7484 return result;
75-
7685 }
7786};
87+
7888```
7989
8090## 拓展
@@ -87,14 +97,14 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
8797}
8898```
8999
90- 可是如果把 ` used[i - 1] == true ` 也是正确的,去重代码如下:
100+ ** 如果改成 ` used[i - 1] == true ` , 也是正确的! ** ,去重代码如下:
91101```
92102if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
93103 continue;
94104}
95105```
96106
97- 这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用` used[i - 1] == false ` ,如果要对树枝前一位去重用用 ` used[i - 1] == true ` 。
107+ 这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用` used[i - 1] == false ` ,如果要对树枝前一位去重用 ` used[i - 1] == true ` 。
98108
99109** 对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!**
100110
@@ -110,5 +120,28 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
110120
111121<img src =' ../pics/47.全排列II3.png ' width =600 > </img ></div >
112122
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+ 就酱,很多录友表示和「代码随想录」相见恨晚,那么大家帮忙多多宣传,让更多的同学知道这里,感谢啦!
114147
0 commit comments