《程序员面试金典》上面的一道题目,leetcode也有这道题
返回某集合的所有非空子集。
给定一个int数组A和数组的大小int n,请返回A的所有非空子集。保证A的元素个数小于等于20,且元素互异。各子集内部从大到小排序,子集之间字典逆序排序,见样例。
测试样例:[123,456,789]
返回:{[789,456,123],[789,456],[789,123],[789],[456 123],[456],[123]}
先按升序排序,再枚举每个元素存在/不存在于集合之中。
class Subset {
public:
vector<vector<int> > getSubsets(vector<int> A, int n) {
// assert A[i] >= 0
if(n <= 0) return vector<vector<int> >();
this->res = vector<vector<int> >();
vector<int> cur;
sort(A.begin(), A.end(), cmp); // 升序排列
helper(A, cur, n, 0);
return this->res;
}
private:
vector<vector<int> > res;
void helper(vector<int>& arr, vector<int>& cur, int n, int k){
if(k == n){
if(cur.size()>0){
this->res.push_back(cur);
}
} else{
cur.push_back(arr[k]);
helper(arr, cur, n, k+1);
cur.pop_back();
helper(arr, cur, n, k+1);
//cur.pop_back();
}
}
static bool cmp(int x, int y){ // 一定要用static!
return x>y;
}
};
《程序员面试金典》上的方法,从包含最小个数元素的集合子集中迭代生成包含全部元素的集合子集。
举例来说:{c, b}的子集有{c, b}, {c}, {b}, {}.
{c, b, a}的子集有 {c, b, a}, {c, a}, {b, a}, {} ∪ {c, b}, {c}, {b}, {}.
可以看出是{c, b, a}的子集可以通过{c, b}的子集生成,左边的粗体集合是{c, b}的子集加上元素a得到的,右边的集合是{c, b}的子集。
class Subset {
public:
vector<vector<int> > getSubsets(vector<int> A, int n) {
if(A.size() == 0 || n <= 0) return vector<vector<int> >();
sort(A.begin(), A.end(), cmp);
vector<vector<int> > a, b, *pa = &a, *pb = &b;
a.push_back(vector<int>());
for(int i = 0; i < A.size(); ++i){
for(int j = 0; j < (*pa).size(); ++j){
(*pb).push_back((*pa)[j]);
(*pb).back().push_back(A[i]);
(*pb).push_back((*pa)[j]);
} swap(pa, pb);
(*pb) = vector<vector<int> >();
} return *pa;
}
private:
static bool cmp(int x, int y){ // 一定要用static!
return x>y;
}
};
对于集合{c, b, a},可以用二进制 2’111表示子集{c, b, a}, 2’101来表示子集{c, a}, 2’011来表示子集{b, a}.
即 i i i-th位为1表示该位置的元素出现在集合中,0表示该位置的元素不出现。
算法的思路,首先按升序排序,再利用二进制位来表示子集。 按 ( 2 n − 1 ) (2^n - 1) (2n−1)到 1 的顺序生成子集. 生成子集时候按A[n-1]到0的顺序.
class Subset {
public:
vector<vector<int> > getSubsets(vector<int> A, int n) {
if(A.size() == 0 || n <= 0) return vector<vector<int> >();
sort(A.begin(), A.end());
vector<vector<int> > res;
unsigned int u = (1 << A.size());
for(unsigned int i = u-1; i > 0; --i){ // i > 0去掉空集合
vector<int> cur;
unsigned int bit = A.size()-1, k = i;
while(k != 0){
if(k&(1<<bit)) {
cur.push_back(A[bit]);
k ^= (1<<bit);
} --bit;
} res.push_back(cur);
} return res;
}
private:
static bool cmp(int x, int y){ // 一定要用static!
return x > y;
}
};
题目来源于 LeetCode 上的Subsets II
Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: [1,2,2]
Output:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
这里主要针对重复的元素,如果元素重复了cnt次,我们应该选择放入[0, cnt]次该元素来生成不同的集合。
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int>& nums) {
res = vector<vector<int> >();
if(nums.empty()) return res;
sort(nums.begin(), nums.end());
cur = vector<int>();
helper(nums, 0);
return res;
}
private:
vector<vector<int> > res;
vector<int> cur;
void helper(vector<int>& nums, int k){
if(k >= nums.size()) {
res.push_back(cur);
return;
}
int cnt = 1;
while(k+1 < nums.size() && nums[k] == nums[k+1]){
++cnt;
++k;
}
helper(nums, k+1);
for(int i = 1; i <= cnt; ++i){
cur.push_back(nums[k]);
helper(nums, k+1);
}
while(cnt--){
cur.pop_back();
}
}
};