「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。
1.题目
全排列 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
思路
举例子:[1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集
一般像这种包含重复元素的,而题解答案中又要求去重的,通用套路就是先排序,排序后相同元素相邻可快速去重
若发现没有选择上一个数,还是以上面的例子来说,我已经选择了1,但是没有选择第一个2,而当前数字与上一个数相 同,则可以跳过当前生成的子集。(因为选择前两个数,或者第一、三个数,都会得到相同的子集)
复杂度分析
我们使用一个 used 数组记录使用过的数字,使用过了就不再使用:
if (used[i]) {
continue;
}
对应上面第二点,如果当前的选项nums[i],与同一层的前一个选项nums[i-1]相同,且nums[i-1]存在,且没有被使用过,则忽略选项nums[i]。
如果nums[i-1]被使用过,它会被第一条修剪掉,不是选项了,即便它和nums[i]重复,nums[i]还是可以选的。
if (i - 1 >= 0 && nums[i - 1] == nums[i] && !used[i - 1]) {
continue;
}
比如[1,1,2],第一次选了第一个1,第二次是可以选第二个1的,虽然它和前一个1相同。 因为前一个1被选过了,它在本轮已经被第一条规则修掉了,所以第二轮中第二个1是可选的。
代码
const permuteUnique = (nums) => {
const res = [];
const used = new Array(nums.length);
nums.sort((a, b) => a - b); // 升序排序
const helper = (path) => {
if (path.length == nums.length) { // 个数选够了
res.push(path.slice()); // path的拷贝 加入解集
return; // 结束当前递归分支
}
for (let i = 0; i < nums.length; i++) { // 枚举出所有的选择
if (used[i]) { // 这个数使用过了,跳过。
continue;
}
if (i - 1 >= 0 && nums[i - 1] == nums[i] && !used[i - 1]) { // 避免产生重复的排列
continue;
}
path.push(nums[i]); // make a choice
used[i] = true; // 记录路径上做过的选择
helper(path); // explore,基于它继续选,递归
path.pop(); // undo the choice
used[i] = false; // 也要撤销一下对它的记录
}
};
helper([]);
return res;
};