题目链接在此
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], [] ]
列出一个集合的所有子集。
很多人知道这样一个规律:一个含有n个元素的集合,他的子集(包括它自己本身)的总数为2^n.
可是你有没想过这个规律背后的道理呢?
我们可以从这个角度解释这个规律:
比如一个集合S={10,20,30}.可以通过保留一些元素,去除一些元素来构造它的子集:
例如“留10去20留30”可以得到{10,30}这个子集,“去10去20去30”可以得到{}这个子集,等等。
也就是说,如果我们用1表示“保留”(或者“有”)、用0表示“去除”(或者“无”),那么每一个子集都可以用一条只含有1和0、长度为|S|的序列来表示。
例如 000表示{},001表示{30},010表示{20},011表示{20,30},100表示{10},101表示{10,30},110表示{10,20},111表示{10,20,30}。
集合中的每一个元素都可以对应为0或1两种情况,组合起来生成子集,那么含有n个元素的集合,自然就有2^n个子集了!
回到这道题来,我们就可以利用这个思路,通过构建出所有的序列,来得出所有的子集。
怎样得到序列呢?明眼人立刻能看出来,上面的那个例子里,000~111这8个序列其实就是0~7的二进制形式。那我们就通过二进制数来构造呗!
以下是我自己的代码,为了简洁,跳跃性比较强。
class Solution {
public:
vector<vector<int> > subsets(vector<int>& nums) {
// 最终结果
vector< vector<int> > v;
// 先要把原先的nums从小到大排序,因为要求"Elements in a subset must be in non-descending order"
std::sort(nums.begin(), nums.end());
// 求出子集总数
int total = pow(2.0, nums.size());
// 从0到2^n-1,每个数都转换成二进制,从而构造一个序列
for(int i = 0; i < total; i++) {
makeSeq(i, nums, v);
}
return v;
}
private:
// 构造一个序列,对应一个子集
void makeSeq(int x, vector<int>& nums, vector< vector<int> >& v) {
vector<int> subset;
// 对于含有n个元素的集合,每个序列长度为n
// 为代码简洁起见,这里并没有把序列这一中间变量给实际的得出来,
// 只是进行了获取二进制的过程(不断模2+除2,进行n次:确保每个二进制长度恰好为n)
// 如果得出的二进制位是1,则代表保留对应位的元素,否则不保留。从而获得一个子集。
for(int i = 0; i < nums.size(); i++) {
if(x % 2) {
subset.push_back(nums[i]);
}
x /= 2;
}
// 将得到的子集放入v
v.push_back(subset);
}
};
当然还有一种比较笨拙但朴实的方法来获取序列:通过构造一颗高度为n(设根节点高度为0)的满二叉树来获取,他的叶子节点就代表一种序列。
例如n为3, 则根节点(第0层)设为000。
第1层的两个节点,修改父节点的第1位(分别修改成0和1),得到000和100
第2层,000的两个子节点,修改父节点的第2位(分别修改成0和1),得到000和010;
100的两个子节点,修改父节点的第2位(分别修改成0和1),得到100和110
第3层,000的两个子节点,修改父节点的第3位(分别修改成0和1),得到000和001;
010的两个子节点,修改父节点的第3位(分别修改成0和1),得到010和011;
100的两个子节点,修改父节点的第3位(分别修改成0和1),得到100和101;
110的两个子节点,修改父节点的第3位(分别修改成0和1),得到110和111
此时已经到第3层(叶子层)了,那么这一层的所有节点就是所有序列:
000,001,010,011,100,101,110,111
class Solution {
public:
vector<vector<int> > subsets(vector<int>& nums) {
vector< vector<int> > v;
sort(nums.begin(), nums.end());
// 初始序列为n个0
string start = "";
for(int i = 1; i <= nums.size(); i++) {
start += '0';
}
makeTree(start, 0, nums, v);
return v;
}
private:
// seq是当前序列,level是层数(同时也表示当前序列要修改的第几位)
void makeTree(string seq, int level, vector<int>& nums, vector< vector<int> >& v) {
// 层数为n,则到了叶子节点,序列已生成
if (level == nums.size()) {
vector<int> subset;
// 遍历序列,如果当前位是1,则代表保留对应位的元素,否则不保留。从而获得一个子集。
for(int i = 0; i < nums.size() ; i++) {
if(seq[i] == '1')
subset.push_back(nums[i]);
}
v.push_back(subset);
return;
}
// 将序列中带修改的那一位,修改为0,然后递归
string tmp0 = seq;
tmp0[level] = '0';
makeTree(tmp0, level + 1, nums, v);
// 将序列中带修改的那一位,修改为1,然后递归
string tmp1 = seq;
tmp1[level] = '1';
makeTree(tmp1, level + 1, nums, v);
}
};
类似的思路在我做过的这道题里也用过。
下面是第三种方法,纯递归。
大体思路:从空集开始,每次复制当前所有的子集,在这些新复制的集合中加入新元素,直至无新元素可加。
其实这样的思路,递不递归都差不多。
class Solution {
public:
vector<vector<int> > subsets(vector<int>& nums) {
vector<vector<int> > v;
std::sort(nums.begin(), nums.end());
addNewElem(v, nums, int(nums.size())-1);
return v;
}
private:
void addNewElem(vector<vector<int> >& v, vector<int>& nums, int index) {
if(index == -1) {
v.push_back(vector<int>());
return;
}
addNewElem(v, nums, index-1);
int total = v.size(); // 此处为何要用一个新变量记录v的size请自行思考.找了我半天bug
for(int i = 0; i < total; i++) {
vector<int> tmp(v[i]);
tmp.push_back(nums[index]);
v.push_back(tmp);
}
}
};