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],
  []
]
这是一道求一个给定集合所有子集的问题,其核心问题在于如何遍历整个子集。

   对于这道题目我的思路是一个straightforward的方法即: 根据子集元素个数进行遍历(0~nums.size()),在每一种子集下面对固定长度(假设为n)的子集,我们可以固定前n-1个元素,激活第n个元素遍历,再固定前n-2个元素,后面两个激活进行遍历。。。。最后,当遍历完整个长度区间的时候我们就得到了该集合的所有子集。 无奈,得出子集的输出顺序与leetcode的测试用例不同,无奈过不了。不过正因为如此,我学习到了两种求子集的方法。

1、回溯法:
  采用递归的方式实现
 1 const int n = 4;
 2 int x[n];
 3 //回溯法
 4 void backtrack(int t)
 5 {
 6     if(t >= n)
 7     {
 8         for(int i = 0; i < n; i++)
 9             cout<<x[i];
10         cout<<endl;
11     }
12     else
13     {
14         for(int i = 0; i <= 1; i++)
15         {
16             x[t] = i;//针对每一个元素进行判断,1表示该元素存在于该子集中,0表示不存在
17             backtrack(t + 1);
18         }
19     }
20 }

    通过对每一个元素的存在与否进行回溯,很容易就遍历了整个集合。

    这段代码核心就是14~18行,我们可以看到当第一次进入时,即从主线程中调用 backtrack(0),程序会执行到14行,这里有两个分支,分别是x[0]=0;x[0]=1;按照程序执行的顺序来看不是并发的。不过在逻辑上是这么执行的。

好了现在到下一个判断(由于t<n,所以第一个if不生效),此时第一个分支执行又会派生出两个分支,分别是x[1]=0,x[1]=1,同样,第一个分支的另一部分也会派生出两个分支,于是我们得到以下组合{x[0],x[1]} = {0,0} or {0,1} or {1,0} or {1,1}。

此时很明显对前两个元素的所有组合做了一个遍历,按照这种结构继续递归,最后(t==n),我们将会对所有的元素子集做出一个遍历(其实就是利用了二叉树的结构,所有的叶子就是我们要的结果)。

 

2、位运算法

    这个方法直接遍历整个子集,没有递归,故运算速度快于第一种。这是某位博主在topcoder上面看到的。我们就时以长技以自强咯。

    整个遍历的思想是很简单的:

    该算法包含两个循环:

    第一重循环是枚举所有子集,共2^n个,即1 << n个
    第二重循环求集合所有j个元素的值,0或1。

    

 1 void bitOperate()
 2 {//对所有子集遍历,每次求出一个子集
 3     for(int i = 0; i < (1 << n); i++)
 4     {
 5         for(int j = 0; j < n; j++)
 6         {//对第i个子集进行筛选
 7             if( (i & (1 << j) ) == 0)
 8                 x[j] = 0;
 9             else
10                 x[j] = 1;
11         }
12         for(int j = 0; j < n; j++)
13             cout<<x[j];
14         cout<<endl;
15     }
16 }

    有很多朋友可能刚刚看到的这段代码回犯迷糊,其实这个为什么要叫位运算法呢?个人认为可能是用了位操作得名的吧。其实它的核心并非是利用了位运算而得到一个遍历的方法,位运算只是起到一个计算工具的作用,其核心还是利用"位"当了一个标志,

怎么说呢。一个大小为n的集合,其子集为2^n个(不懂的回去看看高中课本),正好我们可以利用二进制数来表示每一个子集(给它们起名字),集合与集合之间不同体现在元素,当元素为数字时,可以更加细化:集合之间不同在于元素的数量和元素的值。这两个正好对应二进制中1的位置。比如一个集合用8即(1000),这就表示第0号元素存在,其他元素不存在的集合。

    了解了这些,就可以在内层中用下标j来进行循环求出下标为j的元素是否存在于这个子集中。保存存在的元素,那么就得到了这个子集了。

     以上为大家介绍了了leetcode Subsets题目的核心算法,当然只是数学方法,具体编程的实现大家可以自己去试试!

posted on 2015-06-11 22:35  Archer-Coder  阅读(178)  评论(0编辑  收藏  举报