原题目
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
解题思路
这个题跟第 46 题唯一的区别是 nums 中有重复的元素,那么就会导致会有相同的解集。
比如说示例1 :
如果还是用 46 的解题思路,那么解集:
[[1,1,2], [1,2,1], [1,1,2], [1,2,1], [2,1,1], [2,1,1]]
- 题目要求全排列,不重复,那么就像 46 题那样用一个对象来标记已选择过的元素即可,让每一条递归路线保证不重复选取元素。
- 本题目主要是如何去掉重复解集,产生重复解集的原因是因为在同一层递归中选择了相同的元素,所以需要判断相邻元素是否相同,相同就跳过。
- 如果当前的选项
nums[i]
,与同一层的上一个选项nums[i - 1]
相同,且nums[i - 1]
有意义(即索引>= 0
),且没有被使用过,那就跳过该选项。
因为nums[i - 1]
如果被使用过,它会被修剪掉,不是一个选项了,即便它和nums[i]
重复,nums[i]
还是可以选的。
比如 (看图解)
第 1 层 选了 1 ,那么第二轮递归的时候,这层的标记都是false,都是没有选过的元素,因为第 2 轮 递归的第一层是全新的一次遍历。此时第 2 轮的第一层就不能选索引为 1 的 1 了
第 2 层 经过第 1 层的选择,这层中索引为 0 的 1 会被标记为已选过,那么此时是能选 索引 1 的 1 的。
代码
var permute = function (nums) {
let res = [];
// 先对数组进行排序,方便去掉重复数字
nums.sort((a, b) => a - b);
// 用一个对象记录已选过的元素
let vis = {};
let ope = (cur) => {
if (cur.length === nums.length) {
res.push(cur);
return;
}
for (let i = 0; i < nums.length; i++) {
// 跳过已选择过的元素
if (vis[i]) {
continue;
}
// 跳过重复的元素
if (i - 1 >= 0 && nums[i] === nums[i - 1] && !vis[i - i]) {
continue;
}
vis[i] = true;
cur.push(nums[i]);
ope(cur.slice());
cur.pop();
vis[i] = false;
}
};
ope([]);
return res;
};
console.log(permute([1, 1, 2]));