LeetCode(78)Subset

题目如下:

Given a set of distinct integers, S, 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 S = [1,2,3], a solution is:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

分析如下:

这道题目非常有意思,它让人找出给定的集合(其中没有重复元素)的所有的子集合。

第一个思考方向,迭代。这里面又需要把一件事情想清楚,结果是要求用non-desceding顺序的,迭代的时候如何实现这一点呢?你可以每生成一个子集合就把这个子集合当做数组排序一下。但是每次都排序,比较花时间。所以可以考虑别的办法。如果对元集合进行排序,迭代的时候按照原集合的排序后的顺序逐步生成子集合呢。这样就快多了,代码量也少了。我一开始写了一个很2的代码,提交过后发现就在leetcode官网上,就有一个代码量减少50%,时间减少90%的好代码了。。。

具体的思路是这样的:

初始状态: []

第0次,加入S[0]: [], [1]

第1次,加入S[1]: [], [1], [2], [1, 2]

第1次,加入S[2]: [], [1], [2], [1, 2], [3], [1,3], [2,3], [1,2,3]

从上面可以看出,第0次->第1次,把S[1]加入到S[0]的每个subset中,形成新的subset(即[2], [1, 2])这堆新的subset和之前S[0]时候的那堆老的subset(即[], [1])一起构成S[1]的结果(即  [], [1], [2], [1, 2])。 

重复这个过程直到把S的每个元素都加入了当前的集合。

第二个思考方向,递归。思路比较简单好想。

第三个思考方向,从数学角度来看,假设原集合有n个元素,那么原集合的子集合的个数是2的n次方,记为2 ^ n。对应着从0~2 ^ n - 12 ^ n个数。这2 ^ n个数如果用二进制表示,可以发现一共有n位。每位要么取0,要么取1。如果第i位取0,则说明元集合的第i个元素不出现在当前新生成的子集合中,反之,如果第i位取1,则说明元集合的第i个元素出现在当前新生成的子集合中。这个思路可以通过比特位操作来实现。

代码如下:

//迭代版一 40ms过大集合,目前最简洁最快速的迭代版,leetcode官网答案。先把S排序,然后用j保留vector变化前的大小,再对vector进行插入操作。
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        int length=(int)S.size();
        vector<vector<int>> res(1);
        if(length==0)
            return res;
        std::sort(S.begin(), S.end());
        for(int i=0;i<S.size();++i){
            int j=(int)res.size();
            while(--j>=0){   //如果写为while(j-->0)也正确,不过更推荐的写法是前者。因为前自增生成左值,不需要额外的开销。详情见小结。
                res.push_back(res[j]);
                res.back().push_back(S[i]);
            }
        }
        return res;
    }
};




//迭代版二 628ms过大集合,我最开始写的很弱的版本。。。。
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
    vector<vector<int> > res_vec_vec;
    set<set<int> > res_set_set;
    set<set<int> >::iterator res_set_it;
    int length=(int)S.size();
    if(length==0)
        return res_vec_vec;
    set<int>  small_set;
    res_set_set.insert(small_set); //inset"[]" as a start.
    for(int i=0;i<length;i++){
        set<set<int> > tmp_res_set=res_set_set;
        for(res_set_it=res_set_set.begin();res_set_it!=res_set_set.end();res_set_it++){
            for(int k=0;k<(int)length;k++){  //bug1 一开始写为了for(int k=0;i<(int)length;k++){
                set<int> tmp_small_set=*res_set_it;
                tmp_small_set.insert(S[k]);
                tmp_res_set.insert(tmp_small_set);
            }
        }
        res_set_set.swap(tmp_res_set);
    }
        
    for(res_set_it=res_set_set.begin();res_set_it!=res_set_set.end();res_set_it++){
        set<int> tmp_small_set=*res_set_it;
        vector<int> tmp_vec;
        for(set<int>::iterator it=tmp_small_set.begin();it!=tmp_small_set.end();it++){
            tmp_vec.push_back(*it);
        }
        res_vec_vec.push_back(tmp_vec);
    }
    return res_vec_vec;
    }
};


//递归版一  52ms过大集合 我写的递归版
class Solution {
public:
    set<set<int> > subsets_(vector<int> &S, int i){
        if(i==-1){
            set<int> set_tmp;
            set<set<int> > set_set_tmp;
            set_set_tmp.insert(set_tmp);
            return set_set_tmp;
        }
        set<set<int> >  set_set_tmp = subsets_(S,i-1); //
        set<set<int> >  set_set_out = subsets_(S,i-1);
        set<set<int> >::iterator set_set_it=set_set_tmp.begin();
        for(set_set_it=set_set_tmp.begin();set_set_it!=set_set_tmp.end();set_set_it++){
            set<int> set_tmp=*set_set_it;
            set_tmp.insert(S[i]);
            set_set_out.insert(set_tmp);
        }
        return set_set_out;
    }
    vector<vector<int> > subsets(vector<int> &S) {
        int length=(int)S.size();
        vector<vector<int>> res;
        set<set<int>> tmp;
        if(length==0)
            return res;
        length--;
        tmp=subsets_(S,length);
        for(set<set<int> >::iterator it=tmp.begin();it!=tmp.end();it++){
            set<int> small_set=*it;
            vector<int> small_vec;
//            std::cout<<std::endl;
            for(set<int>::iterator small_it=small_set.begin();small_it!=small_set.end();small_it++){
                small_vec.push_back(*small_it);
//                std::cout<<"*small_it="<<*small_it<<"\t";
            }
            res.push_back(small_vec);
        }
        return res;
    }
};

//模拟2的n次方的版本  leetcode官网答案  40ms过大集合
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        vector< vector<int> > result;
        sort(S.begin(), S.end());
        // Loop from 0 to 2^n - 1
        for (int x = 0; x < (1 << S.size()); ++x) {
            vector<int> sln;
            for (int i = 0; i < S.size(); ++i)
                // If the i-th least significant bit is 1, then choose the i-th integer
                if (x & (1 << i))
                    sln.push_back(S[i]);
            result.push_back(sln);
        }
        return result;
    }
};

小结:

(1) 关于前自增和后自增。在前面的迭代版一种,看到了前自增和后自增操作符。二者的区别是?

int i = 0, j;
j = ++i; // j = 1 , i = 1:prefix yields incremented value
j = i++; // j = 1 , i = 2:postfix yields unincremented value
左值:可以出现在赋值操作左边的值。非const左值可读可写。
右值:可用于赋值操作的右边但不能用于左边的值。右值只能读不能写。
由于后置操作符要返回未加1前的值作为操作的结果,所以必须要保存操作数原来的值,对于比较复杂的类型,这种额外工作可能会花费更大的代价。建议多使用前置操作符。

(2) 关于比特操作
模拟2的N次方的版本中,使用到了比特操作。

                if (x & (1 << i))

这句话的意思是是什么呢?首先理解i和x。i代表了输入集合S的第i位,x代表了取值范围在0~2 ^ n -1的数字。如果x的第i位为0,那么输入集合中的元素S[i]就出现再当前子集合中,否则不出现。这句话就是去判断S[i]的第i位是不是为1的。另外这句话如果像下面这么写也是可以等价的

                if (( x >> i ) & 1)


参考资料:

(1) http://discuss.leetcode.com/questions/253/subsets

(2) http://www.cnblogs.com/maxwellp/archive/2012/02/11/2346844.html


update: 2014- 12-17

思路类似上面的迭代版1,速度非常快。16ms

class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        std::sort(S.begin(), S.end());
        vector<vector<int> > final(1);
        for (int i = 0;  i < S.size(); ++i) {
            int length = final.size();
            for (int j = 0; j < length; ++j) {
                //std::cout<<"j="<<j<<std::endl;
                vector<int> temp = final[j];
                temp.push_back(S[i]);
                final.push_back(temp);
            }
        }
        return final;
    }
};

update: 2014-12-17

思路类似上面的bit 操作版,16ms。 我还不太熟悉用 x & (1<<s.size())这种写法来表达,所以下面写出来的代码看起来比较丑比较原始。但是思路和比特操作的思路一样。

//版本二: bit operation 
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        std::sort(S.begin(), S.end());
        vector<vector<int> > final;
        vector<int> every;
        int total = pow(2, S.size());
        int j = 0;
        for (int i=0; i < total; ++i) {
            int current = i;
            while(current>0) {
                if (current & 1) {
                    every.push_back(S[j]);
                }
                current = current>>1;
                j++;
            }
            final.push_back(every);
            every.clear();
            j = 0;
        }
        return final;
    }
};

update: 2015-03-24

迭代版第一版本, 用result和tmp两个vector倒腾

//10ms
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
       sort(S.begin(), S.end());
       vector<vector<int> > result(1); 
       vector<vector<int> > tmp;
       for (int i = 0; i < S.size(); ++i) {
           int length = result.size();
           tmp.assign(result.begin(), result.end());
           for (int j = 0; j < length; ++j) {
               tmp.push_back(result[j]);
               tmp.back().push_back(S[i]);
           }
           result.swap(tmp);
       }
       return result;
    }
};


迭代版第二版本,其实只用result这1个vector就够了。

//9ms
class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
       sort(S.begin(), S.end());
       vector<vector<int> > result(1); 
       for (int i = 0; i < S.size(); ++i) {
           int length = result.size();
           for (int j = 0; j < length; ++j) {
               result.push_back(result[j]);
               result.back().push_back(S[i]);
           }
       }
       return result;
    }
};

DFS递归版

class Solution {
public:
    // void print_vector(vector<int> v) {
    //     for (int i=0; i < v.size(); ++i) {
    //         std::cout<<v[i]<<"\t";
    //     }
    //     std::cout<<std::endl;
    // }
    void helper(vector<int> &S, vector<int> &every,  vector<vector<int> > &final, int begin, int end) {
        for (int i = begin; i < end; ++i) {
            every.push_back(S[i]);
            final.push_back(every);
            helper(S, every, final, i + 1, end);
            every.pop_back();
        }
    }
    vector<vector<int> > subsets(vector<int> &S) {
        vector<vector<int> > final(1);
        vector<int> every;
        sort(S.begin(), S.end());
        int start = 0;
        helper(S, every, final, start, (int)S.size());
        return final;
    }
};


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值