# 排列问题(二) ## 47.全排列 II 题目链接:https://leetcode-cn.com/problems/permutations-ii/ 给定一个可包含重复数字的序列 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 ## 思路 这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 这里又涉及到去重了。 在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)我们分别详细讲解了组合问题和子集问题如何去重。 那么排列问题其实也是一样的套路。 **还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了**。 我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图: ![47.全排列II1](https://img-blog.csdnimg.cn/20201124201331223.png) 图中我们对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重。 **一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果**。 在[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)中已经详解讲解了排列问题的写法,在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) 、[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下: ## C++代码 ``` class Solution { private: vector> result; vector path; void backtracking (vector& nums, vector& used) { // 此时说明找到了一组 if (path.size() == nums.size()) { result.push_back(path); return; } for (int i = 0; i < nums.size(); i++) { // used[i - 1] == true,说明同一树支nums[i - 1]使用过 // used[i - 1] == false,说明同一树层nums[i - 1]使用过 // 如果同一树层nums[i - 1]使用过则直接跳过 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } if (used[i] == false) { used[i] = true; path.push_back(nums[i]); backtracking(nums, used); path.pop_back(); used[i] = false; } } } public: vector> permuteUnique(vector& nums) { result.clear(); path.clear(); sort(nums.begin(), nums.end()); // 排序 vector used(nums.size(), false); backtracking(nums, vec, used); return result; } }; ``` ## 拓展 大家发现,去重最为关键的代码为: ``` if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } ``` **如果改成 `used[i - 1] == true`, 也是正确的!**,去重代码如下: ``` if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { continue; } ``` 这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用`used[i - 1] == false`,如果要对树枝前一位去重用`used[i - 1] == true`。 **对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!** 这么说是不是有点抽象? 来来来,我就用输入: [1,1,1] 来举一个例子。 树层上去重(used[i - 1] == false),的树形结构如下: ![47.全排列II2](https://img-blog.csdnimg.cn/20201124201406192.png) 树枝上去重(used[i - 1] == true)的树型结构如下: ![47.全排列II3](https://img-blog.csdnimg.cn/20201124201431571.png) 大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。 ## 总结 这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写: ``` if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } ``` 和这么写: ``` if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { continue; } ``` 都是可以的,这也是很多同学做这道题目困惑的地方,知道`used[i - 1] == false`也行而`used[i - 1] == true`也行,但是就想不明白为啥。 所以我通过举[1,1,1]的例子,把这两个去重的逻辑分别抽象成树形结构,大家可以一目了然:为什么两种写法都可以以及哪一种效率更高! 是不是豁然开朗了!! > **相信很多小伙伴刷题的时候面对力扣上近两千道题目,感觉无从下手,我花费半年时间整理了Github项目:「力扣刷题攻略」[https://github.com/youngyangyang04/leetcode-master](https://github.com/youngyangyang04/leetcode-master)。 里面有100多道经典算法题目刷题顺序、配有40w字的详细图解,常用算法模板总结,以及难点视频讲解,按照list一道一道刷就可以了!star支持一波吧!** * 公众号:[代码随想录](https://img-blog.csdnimg.cn/20201210231711160.png) * B站:[代码随想录](https://space.bilibili.com/525438321) * Github:[leetcode-master](https://github.com/youngyangyang04/leetcode-master) * 知乎:[代码随想录](https://www.zhihu.com/people/sun-xiu-yang-64) ![](https://img-blog.csdnimg.cn/2021013018121150.png)