生成子集 subset

《程序员面试金典》上面的一道题目,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;
    }
};

迭代法,从包含i-1个元素的集合的子集生成i个元素的集合子集

《程序员面试金典》上的方法,从包含最小个数元素的集合子集中迭代生成包含全部元素的集合子集。
举例来说:{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) (2n1)到 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();
        }
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值