leetcode78、90-Subsets I/II(组合数/子集数目)

Subsets I 问题描述:

Given a set of distinct integers, nums, return all possible subsets.

Note:
Elements in a subset must be in non-descending order.
The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

给定一个无重复数的序列,求其包含子集数目。要求子集中的元素非递减排列;无重复子集。

问题求解:

由组合数学知识可知,一个有n个元素的序列,包含子集的数目是2^n个。

1、位操作。时间复杂度:O(n*2^n)

对于集合中的每一元素,只有两种可能:要么选取,要么不选取,因此形成了不同的子集和。这种思想和二进制的比特位一致,故可以用位操作来求解。

以集合s={1 , 2 , 3 }为例,子集和数目为2^3。

 元素       子集中可能的结果
  1   ->    选(1) or 不选(0) = 2 
  2   ->    选(1) or 不选(0) = 2  
  3   ->    选(1) or 不选(0) = 2 

可以用一个下标0和1表示是否选择该数字,那么每一组3个0和1的组合表示一种选择,3位共有8种选择。因此:

子集数 = 2*2*2 = 2^3 = { { } , {1} , {2} , {3} , {1,2} , 
{1,3} , {2,3} , {1,2,3} }

按比特位的形式:从低位到高位。如1th个比特位代表是否选取1.

1th bit to 1 , 2th bit to 2 , 3th bit to 3

用二进制的0,1分布情况来表示是否选取某个元素

  3th   2th   1th
  j>>2  j>>1  j>>0
0) 0     0     0  -> 不选3, 不选2, 不选1 = { } 
1) 0     0     1  -> 不选3, 不选2, 选1 = {1} 
2) 0     1     0  -> 不选3, 选2, 不选1 = {2} 
3) 0     1     1  -> 不选3, 选2, 选1 = {1, 2} 
4) 1     0     0  -> 选3, 不选2, 不选1 = {3} 
5) 1     0     1  -> 选3, 不选2, 选1 = {1, 3} 
6) 1     1     0  -> 选3, 选2, 不选1 = {2, 3} 
7) 1     1     1  -> 选3, 选2, 选1 = {1, 2, 3} 

对于s的第1个元素s[0]=1,依次考察j=0~7,如果其二进制表示的第1位是1(j>>0 & 1==1),则选择s[0]加入第j+1个子集和,否则不选。

对于s的第2个元素s[1]=2,依次考察j=0~7,如果其二进制表示的第2位是1(j>>0 & 1==1),则选择s[1]加入第j+1个子集和,否则不选。

对于s的第3个元素s[2]=3,依次考察j=0~7,如果其二进制表示的第3位是1(j>>0 & 1==1),则选择s[2]加入第j+1个子集和,否则不选。

代码如下:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();//数组元素个数
        int nres=pow(2, n);//形成的组合数
        vector<vector<int>> res(nres, vector<int> ());
        sort(nums.begin(), nums.end());//(1)数组排序(要求非递增)
        //(2)0~nres-1正好是n位2进制可以组成的数
        for(int i=0;i<n;i++)
        {//(3)对于每一个比特位(共n个)i
            for(int j=0;j<nres;j++)
            {//(4)对于0~nres中每一个数j
                if((j>>i) & 1)
                {//(5)依次考察j对应的第i位:1则选取,0则不选取
                    res[j].push_back(nums[i]);
                }
            }
        }
        return res;
    }
};

2、递归

思想:可以当做遍历一颗二叉树,向左则选取,向右则不选取。每当该分支已遍历完n个数,则将该子集合加入到结果数组。深搜的思想,时间复杂度O(2^n),空间复杂度O(n)。

#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        if(n==0) return res;
        vector<int> sub;
        sort(nums.begin(), nums.end());//(1)数组排序
        subsearch(nums, sub, 0);//(2)深搜
        return res;
    }

private:
    vector<vector<int>> res;
    void subsearch(vector<int>& nums, vector<int> sub, int step)
    {
        if(step == nums.size())
        {//(1)每当该分支已遍历完n个数,则将该子集合加入到结果数组并返回
            res.push_back(sub);
            return;
        }
        //(2)不选取nums[step]
        subsearch(nums, sub, step+1);
        //(3)选取nums[step]
        sub.push_back(nums[step]);//step为0~n-1
        subsearch(nums, sub, step+1);
        //sub.pop_back();//可加可不加!!!!
    }
};

int main()
{
    Solution s;
    vector<int> num = {3,2,1};
    vector<vector<int> > result = s.subsets(num);
    for(int i = 0;i < result.size();++i){
        for(int j = 0;j < result[i].size();++j){
            cout<<result[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

3、回溯。

利用回溯法+深搜,依次生成指定元素数目(0~n)的子集和。

#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        if(n==0) return res;
        vector<int> sub;
        sort(nums.begin(), nums.end());//(1)数组排序
        res.push_back(sub);//(2)加入空集
        for(int nsub=1;nsub<=n;nsub++)
        {//(3)加入其它子集,nsub表示当前生成的是包含多少个元素的子集
            subsearch(nums, sub, nsub, 0);
        }
        return res;
    }

private:
    vector<vector<int>> res;
    void subsearch(vector<int>& nums, vector<int> sub, int nsub, int index)
    {
        if(nsub == sub.size())
        {//(1)当前子集元素个数已达到nsub则加入
            res.push_back(sub);
            return;
        }
        for(int i=index;i<nums.size();i++)
        {//(2)从下标为index的元素往后遍历
            sub.push_back(nums[i]);//(3)遍历到的元素加入子集合
            subsearch(nums, sub, nsub, i+1);//(4)深搜
            sub.pop_back();//(5)回溯
        }
    }
};

int main()
{
    Solution s;
    vector<int> num = {3,2,1};
    vector<vector<int> > result = s.subsets(num);
    for(int i = 0;i < result.size();++i){
        for(int j = 0;j < result[i].size();++j){
            cout<<result[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

Subsets II 问题描述:

Given a collection of integers that might contain duplicates, nums, return all possible subsets.

Note:
Elements in a subset must be in non-descending order.
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],
  []
]

所给序列有重复元素,如何求其子集合数目。

问题求解:

参照上题第三种方法,回溯,只是在遍历的时候加入判断语句即可

if(i>index && nums[i]==nums[i-1]) continue;

代码如下:

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        int n=nums.size();
        if(n==0) return res;
        vector<int> sub;
        sort(nums.begin(), nums.end());//(1)数组排序
        res.push_back(sub);//(2)加入空集
        for(int nsub=1;nsub<=n;nsub++)
        {//(3)加入其它子集,nsub表示当前生成的是包含多少个元素的子集
            subsearch(nums, sub, nsub, 0);
        }
        return res;
    }
private:
    vector<vector<int>> res;
    void subsearch(vector<int>& nums, vector<int> sub, int nsub, int index)
    {
        if(nsub == sub.size())
        {//(1)当前子集元素个数已达到nsub则加入
            res.push_back(sub);
            return;
        }
        for(int i=index;i<nums.size();i++)
        {//(2)从下标为index的元素往后遍历
            if(i>index && nums[i]==nums[i-1]) continue;//(3)在此处加入是否重复的判断!!!!
            sub.push_back(nums[i]);//(4)遍历到的元素加入子集合
            subsearch(nums, sub, nsub, i+1);//(5)深搜
            sub.pop_back();//(6)回溯
        }
    }
};

方法二:与方法一稍有不同,但是是回溯法的基本应用。

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        int n = nums.size();
        if(n==0) return res;
        vector<int> sub;
        sort(nums.begin(), nums.end());
        subsearch(nums, sub, 0);
        return res;
    }
private:
    vector<vector<int>> res;
    void subsearch(vector<int>& nums, vector<int>& sub,  int pos)
    {
        res.push_back(sub);//(1)
        for(int i = pos; i < nums.size(); i++) 
        {
            if(i > pos && nums[i] == nums[i-1])  continue;//(2)
            sub.push_back(nums[i]);//(3)
            subsearch(nums, sub, i + 1);//(4)
            sub.pop_back();//(5)
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值