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}.
使用遞歸很容易寫出如下代碼:
// 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;直到所有元素都取完畢。一種實現代碼如下:
// 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)中的方法求所有子集,然后對子集去重復。但是這種方法並不完美。
可以將原始集合排序,然后每次選擇元素時,判斷是否與之前選擇的元素是否相同,從而進行去重復。一種代碼如下:
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