Given a collection of integers that might contain duplicates, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,2]
, a solution is:
[ [2], [1], [1,2,2], [2,2], [1,2], [] ]
这道题牵扯一点数学知识。
求子集,首先我们来看一个证明:
n=1时 明显有两个子集
假设n=k,k>=2时有2^k个子集
则n=k+1时
新增加的元素与原来的子集又可构成2^k个子集
故此时总共有2^k+2^k=2^(k+1)个子集
故该命题在n>=2时也成立
故命题正确
集合中的每个元素都有两种可能,在集合或不在集合,好像0和1,那么n个元素构成的子集数目就是2^n。
所以对于重复元素,我们可以把它看成一个特殊元素,举例:1, 2, 2 ,元素2具有不在集合,第一个2在集合中,第二个2在集合中三种可能,但我们把它看成一个元素,理解成一个大“2”。所以,我们要对该特殊元素配备一个计数器,用来计数它的数目。普通元素只加入集合一次就可以了,该特殊元素可以加入多次。
我们对这道题首先需要排序,不然难以找到相同元素。
将计数器count初始化为1,因为任意普通元素也至少要加入集合一次。当重复多次时,++count,下次直接跳过重复元素就可以,因为把它看成了一个整体。
代码如下:
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> totalset = {{}};
const int size = nums.size();
sort(nums.begin(), nums.end()); /可不要忘记排序
for(int i=0; i<size; ){
int count = 1;
while(i+count<size && nums[i] == nums[i+count])
++count;
int prev_size = (int)totalset.size();
for(int j=0; j<prev_size; ++j){
vector<int> vec = totalset[j];
for(int k=0; k<count; ++k){
vec.push_back(nums[i]);
totalset.push_back(vec);
}
}
i += count;
}
return totalset;
}
};
另外一种思路,回溯法:
先深入进去,没路了再回来。
该方法打印顺序为,以[1, 2, 2]为例:
空, 1, 12,122,2, 22
起始这种方法就和我们最普通的形式差不多,从第一个元素开始,打印长度为1,长度为2,...,然后再从第二个元素继续。。。
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> totalset = {{}};
vector<int> vec;
const int size = nums.size();
sort(nums.begin(), nums.end());
handle_detail(totalset, nums, vec, size, 0);
return totalset;
}
private:
void handle_detail(vector<vector<int>>& totalset, const vector<int>& nums,
vector<int>& vec, const int size, int cur){
for(int i=cur; i<size; ++i){
if(i > cur && nums[i] == nums[i-1])
continue;
vec.push_back(nums[i]);
totalset.push_back(vec);
handle_detail(totalset, nums, vec, size, i+1);
vec.pop_back();
}
}
};