原题链接
建议先练习:全排列|
一. 题目描述
给定一个可包含重复数字的序列 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
二. 解题思路
这个题目和之前的全排列一相似,唯一不相同的地方就是在上一题中明确规定数组中不存在重复元素,但是这个里面存在重复的元素,所以我们自然而然的可以想到去重,那该怎么去重呢,原理很简单,与之前组合一和组合二的去重方法相同,建议大家可以先去练习一下。
老套路:递归三件套!!但是这个在做之前得将原数组nums 排序,保证重复的元素在一起,这样才方便进行去重操作。
1. 确定递归条件:和之前全排列一的思路相同,只需要一个nums 原数组和 used 数组记录path 已经加入的元素。
2. 确定递归的终止条件:由于是递归,所以也很简单,只需要path.size() == nums.size() 的时候,将结果收集到res 中再return 即可。
3. 单层递归原则:首先我们得进行去重操作:如下图所示,当第一层中选取了第一个1的时候,回溯之后就没必要选取第二个1了,因为同一层选取相同的元素势必会出现相同的结果,所以直接剪枝即可,但是怎么剪枝呢?(1)判断当前元素和前一个元素是否相同,即 i > 0 && nums[i] == nums[i - 1]。(2) 判断当前元素是否是同一层相同元素回溯得来,即 used[i - 1] == false,因为在回溯之前会选取 nums[i - 1] ,这时候会将used[i - 1] 赋值为 true, 在回溯结束之后,会将 used[i - 1] 重新赋值为 false,这就是在树层上去重的一个操作。其次,在进行树层上的剪枝之后,需要进行树枝上的剪枝,即判断 used[i] 时候为 true ,如果为true ,说明在path 中已经选取了该元素,continue 即可。最后就是熟悉的递归操作,将used[i] 赋值为 true,并在 path 中添加 nums[i] ,然后进行递归,递归结束后回溯即可。
写了一大堆,可能可读性不是很好,希望大家可以看懂。
话不多说!!!上代码!!
三. 代码
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void back(vector<int>& nums, vector<bool> used){
if(path.size() == nums.size()) {
res.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i++){
if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
continue;
}
if(used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
back(nums, used);
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
back(nums, used);
return res;
}
};
四. 总结
本题还是属于回溯里面比较进阶的题目吧,建议大家练习,但是一定要懂得每一步的作用以及结果,写代码一定要勤思考,多思考就能印象深刻。加油!!我们共勉!
时间复杂度:O(n! * n)
空间复杂度:O(n)