「这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战」
Hope is a good thing, maybe the best of things. And no good thing ever dies.
前言
回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。
深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」
题目
给定一个可包含重复数字的序列 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]]
思路分析
和 回溯算法-全排列 的差异主要是:数字序列是可以重复的。
本题难点在于正确、高效的去重
通常处理包含重复的序列,为了方便在迭代中识别出重复,先对 nums 中数字升序排序,使得相同的数字相邻。
- 我们使用一个
used数组记录使用过的数字,使用过了就不再使用。 - 如果当前的选项
nums[i],与同一层的前一个选项nums[i-1]相同,且nums[i-1]存在,且没有被使用过,则忽略选项nums[i] - 经过了充分的剪枝,当数字选够了,就可以直接加入解集,结束递归
解题
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function(nums) {
const ans = [];
const used = new Array(nums.length).fill(false); // 记录使用过的数字
const backtrack = (idx, perm) => {
if (idx === nums.length) { // 执行结束加入解集
ans.push(perm.slice());
return;
}
for (let i = 0; i < nums.length; ++i) {
if (used[i] || (i > 0 && nums[i] === nums[i - 1] && !used[i - 1])) {
continue; // 结束当前for循环,进入下一个for循环
}
perm.push(nums[i]);
used[i] = true;
// 递归
backtrack(idx + 1, perm);
used[i] = false;
perm.pop();
}
}
// 进行排序从小到大
nums.sort((x, y) => x - y);
backtrack(0, []);
return ans;
};
附:leetcode-cn.com/problems/pe…
结语
如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。
文章如有错误之处,希望在评论区指正🙏🙏
欢迎关注我的微信公众号,一起交流技术,微信搜索 🔍 :「 五十年以后 」