集合子集

1)集合子集

对于给定的集合S={1,2,3},求其所有子集。LintCode

一种通常的做法是:对于集合中的任意一个元素e,有两种可能:被选中作为子集中的元素,或否。因此,一个包含N个元素的集合,共有2^N个子集。如上例,其所有子集如下:

s0={}, s1={1}, s2={2}, s3={3}, s4={1,2}, s5={2,3}, s6={1,3}, s7={1,2,3}.

使用递归很容易写出如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Method 1 recursion
class  Solution {
     void  subsetcore(vector< int > &nums, int  pos,
         vector<vector< int > > &vs, vector< int > &v){
         if  (pos==nums.size()){
             vs.push_back(v);         // get one subset
             return ;
         }
         v.push_back(nums[pos]);      // select this element
         subsetcore(nums,pos+1,vs,v);
         v.pop_back();                // not select
         subsetcore(nums,pos+1,vs,v);
     }
public :
     /**
      * @param S: A set of numbers.
      * @return: A list of lists. All valid subsets.
      * Tip: eacho element has two state: to be selected and not
      */
     vector<vector< int > > subsets(vector< int > &nums) {
         // write your code here
         vector<vector< int > > vs;
         vector< int > v;
         sort(nums.begin(),nums.end());
         subsetcore(nums,0,vs,v);
         return  vs;
     }
};

其中数组v是当前正在生成的子集,pos是当前选择的元素在原集合(nums)中的位置。直到pos等于原集合的大小,说明寻找到一个子集,则将其加入到结果vs中。其中24行为排序,可以保证最终求得的子集按照字典序,如非必要,可以不用排序。这是回溯法(backtrack)的典型应用。

由于使用递归,其速度往往很慢。假如原集合中有20个元素,则其递归调用了2^20次,很明显容易出现栈溢出

2)集合子集非递归版

仍以上例,对于一个集合S'={1,2},其子集共有四个{},{1},{2},{1,2}。集合S=S'+{3},因此,S的子集=S'的子集+{S'的子集+{3}}。同理,集合S''={1}的子集为{},{1}。

因此可以使用下面方法:初始一个空子集{};从原始集合中取一个元素e,将现在所求得的所有子集复制,并加入元素e;直到所有元素都取完毕。一种实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Method 2 loop
class  Solution {
public :
     /**
      * @param S: A set of numbers.
      * @return: A list of lists. All valid subsets.
      *
      * Tip: all subsets count: 2^n (include null)
      * sketch : set { 1, 2, 3 }
      * [ ]
      * [ ] [ ] -> [ ] [1]
      * [ ] [1] [ ] -> [ ] [1] [2] [1,2]
      * [ ] [1] [2] [1,2] [ ] -> [ ] [1] [2] [1,2] [3] [1,3] [2,3] [1,2,3]
      */
     vector<vector< int > > subsets(vector< int > &nums) {
         // write your code here
         vector<vector< int > > vs(1);
         for  ( int  i=0; i<nums.size(); ++i){
             int  size = vs.size();
             for  ( int  j=0; j<size; ++j){
                 vs.push_back(vs[j]);
                 vs.back().push_back(nums[i]);
             }
         }
         return  vs;
     }
};

其中20~23行,将当前求得的所有子集复制,并加入当前元素nums[i]。

3)具有重复元素的集合子集

从严格意义讲,集合中无重复元素,但是在实际编程时,很多问题将转化成求具有重复元素的集合子集问题。很明显,求得的所有子集,必须唯一。LintCode

一种简单的思路:仍然按照1)、2)中的方法求所有子集,然后对子集去重复。但是这种方法并不完美。

可以将原始集合排序,然后每次选择元素时,判断是否与之前选择的元素是否相同,从而进行去重复。一种代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class  Solution {
public :
     /**
      * @param S: A set of numbers.
      * @return: A list of lists. All valid subsets.
      * Tip : See problem 18 subsets
      */
     vector<vector< int > > subsetsWithDup( const  vector< int > &S) {
         // write your code here
         vector< int > A(S);
         sort(A.begin(),A.end());
         vector<vector< int > > vs(1);
         for  ( int  i=0,count=0; i<A.size(); ++i){
             int  size = vs.size();
             for  ( int  j=0; j<size; ++j){
                 if  (i==0 || A[i-1]!=A[i] || j>=count){
                     vs.push_back(vs[j]);
                     vs.back().push_back(A[i]);
                 }
             }
             count=size;
         }
         return  vs;
     }
};

其中16~19行,如果之前选择的元素为空,或者之前的元素与当前元素不同,或者,所选择的总数大于之前的子集,则认为是一个新的无重复子集。

注:以上代码可参见 https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/subsets.cpp

https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/subsets-ii.cpp

标签: 回溯法(backtrack),

https://www.cnblogs.com/eudiwffe/p/6558744.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值