欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

# 排列问题(二) ## 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://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 这道题目和[46.全排列](https://programmercarl.com/0046.全排列.html)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 这里又涉及到去重了。 在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html) 、[90.子集II](https://programmercarl.com/0090.子集II.html)我们分别详细讲解了组合问题和子集问题如何去重。 那么排列问题其实也是一样的套路。 **还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了**。 我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图: ![47.全排列II1](https://img-blog.csdnimg.cn/20201124201331223.png) 图中我们对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重。 **一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果**。 在[46.全排列](https://programmercarl.com/0046.全排列.html)中已经详解讲解了排列问题的写法,在[40.组合总和II](https://programmercarl.com/0040.组合总和II.html) 、[90.子集II](https://programmercarl.com/0090.子集II.html)中详细讲解的去重的写法,所以这次我就不用回溯三部曲分析了,直接给出代码,如下: ## C++代码 ```CPP 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, 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]的例子,把这两个去重的逻辑分别抽象成树形结构,大家可以一目了然:为什么两种写法都可以以及哪一种效率更高! 是不是豁然开朗了!! ## 其他语言版本 java: ```java class Solution { //存放结果 List> result = new ArrayList<>(); //暂存结果 List path = new ArrayList<>(); public List> permuteUnique(int[] nums) { boolean[] used = new boolean[nums.length]; Arrays.fill(used, false); Arrays.sort(nums); backTrack(nums, used); return result; } private void backTrack(int[] nums, boolean[] used) { if (path.size() == nums.length) { result.add(new ArrayList<>(path)); return; } for (int i = 0; i < nums.length; 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; } //如果同⼀树⽀nums[i]没使⽤过开始处理 if (used[i] == false) { used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用 path.add(nums[i]); backTrack(nums, used); path.remove(path.size() - 1);//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复 used[i] = false;//回溯 } } } } ``` python: ```python class Solution: def permuteUnique(self, nums: List[int]) -> List[List[int]]: # res用来存放结果 if not nums: return [] res = [] used = [0] * len(nums) def backtracking(nums, used, path): # 终止条件 if len(path) == len(nums): res.append(path.copy()) return for i in range(len(nums)): if not used[i]: if i>0 and nums[i] == nums[i-1] and not used[i-1]: continue used[i] = 1 path.append(nums[i]) backtracking(nums, used, path) path.pop() used[i] = 0 # 记得给nums排序 backtracking(sorted(nums),used,[]) return res ``` Go: ```go var res [][]int func permute(nums []int) [][]int { res = [][]int{} backTrack(nums,len(nums),[]int{}) return res } func backTrack(nums []int,numsLen int,path []int) { if len(nums)==0{ p:=make([]int,len(path)) copy(p,path) res = append(res,p) } used := [21]int{}//跟前一题唯一的区别,同一层不使用重复的数。关于used的思想carl在递增子序列那一题中提到过 for i:=0;i { return a - b }) let result = [] let path = [] function backtracing( used) { if (path.length === nums.length) { result.push(path.slice()) return } for (let i = 0; i < nums.length; i++) { if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) { continue } if (!used[i]) { used[i] = true path.push(nums[i]) backtracing(used) path.pop() used[i] = false } } } backtracing([]) return result }; ``` Go: 回溯+本层去重+下层去重 ```golang func permuteUnique(nums []int) [][]int { var subRes []int var res [][]int sort.Ints(nums) used:=make([]bool,len(nums)) backTring(nums,subRes,&res,used) return res } func backTring(nums,subRes []int,res *[][]int,used []bool){ if len(subRes)==len(nums){ tmp:=make([]int,len(nums)) copy(tmp,subRes) *res=append(*res,tmp) return } // used[i - 1] == true,说明同一树支candidates[i - 1]使用过 // used[i - 1] == false,说明同一树层candidates[i - 1]使用过 for i:=0;i0&&nums[i]==nums[i-1]&&used[i-1]==false{//当本层元素相同且前一个被使用过,则继续向后找(本层去重) continue } //到达这里有两种情况:1.该层前后元素不同;2.该层前后元素相同但该层没有使用过 //所以只能对该层没有被使用过的抽取 if used[i]==false{ //首先将该元素置为使用过(即同一树枝使用过),下一层的元素就不能选择重复使用过的元素(下层去重) used[i]=true subRes=append(subRes,nums[i]) backTring(nums,subRes,res,used) //回溯 //回溯回来,将该元素置为false,表示该元素在该层使用过 used[i]=false subRes=subRes[:len(subRes)-1] } } } ``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)