给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
一:迭代
思路: 先排序,在当前所有子集的基础上添加新的元素并构成新的子集,但这样会导致重复构建,为避免重复,需要一个指针标记新增子集的索引位置
橘色表示新增子集,黑色表示重复子集,由于数字2的重复,在第四行开始之前,需要标记上一行(第三行)新增的子集的起始处,也就是第三行橘黄色的[ 2 ],在其及其之后的子集上添加2即可,
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
res.push_back({});
int neNum = 0; //新增子集的数目
int start = 0; //指针位置
for(int i=0; i<nums.size(); i++){
int size = res.size();
//定位start起始位置
if(i > 0 && nums[i] == nums[i-1]){
start += neNum;
}
else start = 0;
//指针及其之后的子集
for(int j=start; j<size; j++){
vector<int> ans = res[j];
ans.push_back(nums[i]);
res.push_back(ans);
}
//新增子集个数
neNum = (res.size() - start)/2;
}
return res;
}
};
二:深搜
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> ans;
dfs(nums, ans, 0);
return res;
}
void dfs(vector<int>& nums, vector<int>& ans, int start){
res.push_back(ans);
for(int i=start; i<nums.size(); i++){
//剪枝,如果连续相同的数,只需取其一,并且取优先出现的那个
if(i > start && nums[i] == nums[i-1]){
continue;
}
ans.push_back(nums[i]);
//start参数即为递归出口
dfs(nums, ans, i+1);
ans.pop_back(); //回溯
}
}
};
三:位运算
对于数组中的每一位数,只有取与不取的选择,取为1,不取则为0;
所以共有2的n次方个结果数,(n为数组长度)
比如:[1 2 2],对于子集[1,2],也就是说对于连续相同的数,只需要取第一个2或者第二个2即可,二者选其一,既然这样,不如优先选择第一个2,
第一行表示三个数,第二行,第三行表示对于子集[1, 2]的两种选择
1 | 2 | 2 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
int totalNum = 1<<nums.size();
for(int i=0; i<totalNum; i++){
vector<int> ans;
bool flag = false;
//遍历,判断每一位是否选取
for(int j=0; j<nums.size(); j++){
//判断每一位是否选取,与1相与,若当前位是1,则选取
if((i>>j&1)==1){
//当前是重复数字,并且前一位是 0,跳过这种情况
if(j>0 && nums[j]==nums[j-1] && (i>>(j-1)&1)==0){
flag = true;
break;
}else{
ans.push_back(nums[j]);
}
}
}
if(!flag){
res.push_back(ans);
}
}
return res;
}
};