Leetcode OJ: Subsets I/II

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],
  []
]

罗列所有的子集,要求每一个子集的元素有序,且无重复子集。而且保证每个元素都是无重复(distinct)的。

很自然,第一想法就是递归,回溯那一套,写完后提交,可惜没通过。。

原因不明,N天过后搜了下才知道需要有序,所以还需要先sort。

第一次AC代码如下:

 1 class Solution {
 2 public:
 3     void subsetsImpl(vector<int>& S, int index, vector<int>& item, vector<vector<int> >& ret) {
 4         if (index == S.size()) {
 5             ret.push_back(item);
 6             return;
 7         }
 8         subsetsImpl(S, index + 1, item, ret);
 9         item.push_back(S[index]);
10         subsetsImpl(S, index + 1, item, ret);
11         item.pop_back();
12     }
13     vector<vector<int> > subsets(vector<int> &S) {
14         vector<vector<int> > ret;
15         vector<int> item;
16         sort(S.begin(), S.end());
17         subsetsImpl(S, 0, item, ret);
18         return ret;
19     }
20 };

时间复杂度O(n*2^n),n为S的长度,2^n是组合个数,无可避免。

在网上还搜到了另外的一些非递归实现的解法

有利用二进制数的,个人觉得这方法约束太大了,因为二进制数超过64位了就得自己去实现一个了,比较麻烦,而且复杂度没有比以上方法提升太多。

还有一种动态规划的方法,还是比较通用的,个人比较推荐,复杂度O(2^n)。

思路就是subset(n) = join(subset(n-1), (subset(n-1), S[n]))

每考虑一个新的元素,就是把这个元素与之前得到的结果都结合一次,并进之前的结果,得出新的结果。

代码如下:

 1 class Solution {
 2 public:
 3     vector<vector<int> > subsets(vector<int> &S) {
 4         sort(S.begin(), S.end());
 5         vector<vector<int> > ret(1);
 6         for (auto it = S.begin(); it != S.end(); ++it) {
 7             int j = ret.size();
 8             while (j--) {
 9                 ret.push_back(ret[j]);
10                 ret.back().push_back(*it);
11             }
12         }
13         return ret;
14     }
15 };

如果元素中有重复怎么办呢?

Given a collection of integers that might contain duplicates, 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,2], a solution is:

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

按照上面提到的动态规划的思想,我们看看有重复的过程会是怎样的。

S=[1, 2, 2]

[]   // 空

[1] // 1与之前的结合

[2]

[1][2] // 2 与之前的所有结合

[2]

[1][2]

[2][2]

[1][2][2]  // 2与之前的结合删掉了2个

这里删除了重复的两个,那么这两个符合什么条件时不可以不考虑的呢?
其实我们认真想想,如果当前元素与上一个元素是重复的,那么当前元素与比上一个元素还要早产生的子集结合是一定重复的,因为上一个元素已经经历了相同的过程了,因此当前元素只能跟上一个元素产生的新子集进行结合才能保证不重复。
于是思路就有了,就是遇到有重复现象时,就不是与之前的所有子集结合,而只与上一个元素产生的新子集进行结合,这里实现就需要记录上一个元素产生子集的起始结束,代码如下:
 1 class Solution {
 2 public:
 3     vector<vector<int> > subsetsWithDup(vector<int> &S) {
 4         sort(S.begin(), S.end());
 5         vector<vector<int> > ret(1);
 6         int pre = 0;
 7         for (auto it = S.begin(); it != S.end(); ++it) {
 8             int upper = ret.size();
 9             int bottom = (it != S.begin() && *it == *(it-1)) ? pre : 0;
10             pre = upper;
11             for (int i = upper - 1; i >= bottom; --i) {
12                 ret.push_back(ret[i]);
13                 ret.back().push_back(*it);
14             }
15         }
16         return ret;
17     }
18 };

结构是一样的,只是加了个upper与bottom的限制。复杂度没变化。

 
posted @ 2014-04-06 02:07  flowerkzj  阅读(465)  评论(0编辑  收藏  举报