解法:回溯算法
class Solution {
public:
vector<vector<int>> res; //保存结果
void backtrack(vector<int>& nums,vector<int> track,int start){
sort(track.begin(),track.end()); //排序
if(find(res.begin(),res.end(),track)==res.end()) //若结果中不包含就加入
res.push_back(track);
for(int i=start;i<nums.size();i++){ //选择路径
track.push_back(nums[i]); //做选择
backtrack(nums,track,i+1);
track.pop_back(); //撤销选择
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<int> track; //路径
backtrack(nums,track,0);
return res;
}
};
优化:枝剪
以 [1, 2, 2’]为例
如果按照子集的剪枝方案将会得到的非空子集为: [[1],[1,2],[1,2,2’],[1,2’],[2],[2,2’],[2’]]
1.出现重复解的为 [2] [2’] 和 [1, 2] [1, 2’]
2.观察选取到重复的解的特点, 重复的解是因为有 2 个或以上个相同的元素(如 2,2’)在同一层回溯过程中被分别选择, 作为最终解的一部分了
3.要在回溯过程中区分出来这种数值相同的情况(就是要区分出来 2 == 2’)
4.因为输入数据已经经过排序, 所以一种显而易见的做法就是在回溯时, 检查当前元素是否和上一个元素相同,即 nums[i - 1] == nums[i] 如果发现和上一个相同了就饿可以直接跳过(如 nums = [1, 2, 2’] nums[1] == nums[2], nums[2] 就直接跳过了)
5.需要注意的是 nums[i - 1] == nums[i] 的作用是在递归树的同一层上的, 也就是防止 nums = [1, 2, 2’] 出现 [1, 2], [1, 2’] 两个重复的解 (即: 通过 nums[1] == nums[2]: continue 进行的剪枝)
6.而解 [1, 2, 2’] 实际中的 2, 2’ 是位于递归树的不同层级的, 也就是剪枝要从每一层的第二个元素开始, 所以还要增加一个 i > start 或者 i != start
7.最终相对于子集 的思路就是要增加一个 i > start && nums[i - 1] == nums[i] 或 i != start && nums[i - 1] == nums[i] 的剪枝
class Solution {
public:
vector<vector<int>> res; //保存结果
void backtrack(vector<int>& nums,vector<int> track,int start){
res.push_back(track);
for(int i=start;i<nums.size();i++){ //选择路径
if(i>start && nums[i]==nums[i-1]) //枝剪:i>start防止越界,前面先进行了排序
continue;
track.push_back(nums[i]); //做选择
backtrack(nums,track,i+1);
track.pop_back(); //撤销选择
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end()); //排序,为了后面枝剪
vector<int> track; //路径
backtrack(nums,track,0);
return res;
}
};