问题:给定一个可能具有重复数字的列表,返回其所有可能的子集。
样例 1:
输入:[0]
输出:
[
[],
[0]
]
样例 2:
输入:[1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
思路:
- 使用回溯法的递归框架;
- 解空间为子集树;
- 对于集合中出现了重复的数字,需要设计剪枝函数check(i)。规定重复元素的选取状态只能是前半部分1,后半部分0,从而排除重复项。例如【1,3,3,3】,对于重复元素3的选取,1 0 0结果是【3】,0 1 0结果也是【3】,0 0 1结果还是【3】。那么只保留第一种情况,0后不能再取1就好了,遇到其余情况便不再继续下递归。
- 除此之外,还有另一位老哥的办法,在去重复元素求子集的基础上再一步一步添加重复元素,记录一下。
跳转到该链接
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
int v[maxn] = {0},x[maxn] = {0};
int backtrack(int i,int n);
int check(int i);
int main(){
printf("求n个元素子集,请输入n的值\n");
int n;
scanf("%d",&n);
printf("请输入集合元素\n");
for(int i = 1 ; i <= n ; i++ )
scanf("%d",&v[i]);
backtrack(1,n);
return 0;
}
int backtrack(int i,int n){
if(i > n ){
cout<<"{ ";
for(int tempi = 1;tempi <= n; tempi++){
if(x[tempi] == 1){
printf("%d",v[tempi]);
}
}
cout<<" }"<<endl;
}else{
for(int j = 0 ; j <= 1 ; j++){
x[i] = j;
if(check(i))
continue;
backtrack(i+1,n);
}
}
}
int check(int i){
if(i != 1 && v[i] == v[i-1] && x[i-1] == 0 && x[i] == 1)
return 1;
else
return 0;
}
LintCode参考代码:
class Solution {
public:
vector<vector<int>> v;
vector<int> tempv;
int x[1000]={0};
vector<vector<int>> subsetsWithDup(vector<int> &nums) {
int n = nums.size();
sort(nums.begin(),nums.end());
backtrack(0,n,nums);
return v;
}
int backtrack(int i,int n,vector<int> nums){
if(i >= n ){
tempv.clear();
for(int tempi = 0;tempi < n; tempi++){
if(x[tempi] == 1)
tempv.push_back(nums[tempi]);
}
v.push_back(tempv);
}else{
for(int j = 0 ; j <= 1 ; j++){
x[i] = j;
if( i!=0 && nums[i] == nums[i-1] && x[i-1] == 0 && x[i] == 1)
continue;
backtrack(i+1,n,nums);
}
}
}
};