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

# 排列问题(二) ## 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; } }; ``` ## 拓展 大家发现,去重最为关键的代码为: ```cpp if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } ``` **如果改成 `used[i - 1] == true`, 也是正确的!**,去重代码如下: ```cpp 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) 大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。 ## 总结 这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写: ```cpp if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } ``` 和这么写: ```cpp 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 }; ``` ### Swift ```swift func permuteUnique(_ nums: [Int]) -> [[Int]] { let nums = nums.sorted() // 先排序,以方便相邻元素去重 var result = [[Int]]() var path = [Int]() var used = [Bool](repeating: false, count: nums.count) func backtracking() { if path.count == nums.count { result.append(path) return } for i in 0 ..< nums.count { // !used[i - 1]表示同一树层nums[i - 1]使用过,直接跳过,这一步很关键! if i > 0, nums[i] == nums[i - 1], !used[i - 1] { continue } if used[i] { continue } used[i] = true path.append(nums[i]) backtracking() // 回溯 path.removeLast() used[i] = false } } backtracking() return result } ``` ### C ```c //临时数组 int *path; //返回数组 int **ans; int *used; int pathTop, ansTop; //拷贝path到ans中 void copyPath() { int *tempPath = (int*)malloc(sizeof(int) * pathTop); int i; for(i = 0; i < pathTop; ++i) { tempPath[i] = path[i]; } ans[ansTop++] = tempPath; } void backTracking(int* used, int *nums, int numsSize) { //若path中元素个数等于numsSize,将path拷贝入ans数组中 if(pathTop == numsSize) copyPath(); int i; for(i = 0; i < numsSize; i++) { //若当前元素已被使用 //或前一位元素与当前元素值相同但并未被使用 //则跳过此分支 if(used[i] || (i != 0 && nums[i] == nums[i-1] && used[i-1] == 0)) continue; //将当前元素的使用情况设为True used[i] = 1; path[pathTop++] = nums[i]; backTracking(used, nums, numsSize); used[i] = 0; --pathTop; } } int cmp(void* elem1, void* elem2) { return *((int*)elem1) - *((int*)elem2); } int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){ //排序数组 qsort(nums, numsSize, sizeof(int), cmp); //初始化辅助变量 pathTop = ansTop = 0; path = (int*)malloc(sizeof(int) * numsSize); ans = (int**)malloc(sizeof(int*) * 1000); //初始化used辅助数组 used = (int*)malloc(sizeof(int) * numsSize); int i; for(i = 0; i < numsSize; i++) { used[i] = 0; } backTracking(used, nums, numsSize); //设置返回的数组的长度 *returnSize = ansTop; *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop); int z; for(z = 0; z < ansTop; z++) { (*returnColumnSizes)[z] = numsSize; } return ans; } ``` -----------------------