《程序员面试金典》(第六版)习题:仅为记录一下以加强印象,不为商业用途,如有侵权请联系删除。以下源码和解释参考了书中源码以及解释。
一个有n个元素的集合一共有
C
n
0
+
C
n
1
+
C
n
2
+
.
.
.
C
n
n
=
2
n
C_n^0+C_n^1+C_n^2+...C_n^n=2^n
Cn0+Cn1+Cn2+...Cnn=2n个子集。也可以这样考虑,将集合的每一个子集与一个有n个二进制位的二进制数对应起来,n个二进制位对应于集合中的n个元素,当集合中的该元素在该子集中时,该二进制位为1,不在时为0。因此一个有n个元素的集合一共有
2
n
2^n
2n个子集。当我们知道由集合中前
n
−
1
n-1
n−1个元素
{
a
0
,
a
1
,
a
2
,
.
.
.
,
a
n
−
2
}
\left\{a_0, \quad a_1, \quad a_2,...,\quad a_{n-2}\right\}
{a0,a1,a2,...,an−2}构成的
2
n
−
1
2^{n-1}
2n−1个子集时,当再加入元素
a
n
−
1
a_{n-1}
an−1时怎么得到由n个元素的所有子集?这时由n个元素构成的所有子集为由集合中前
n
−
1
n-1
n−1个元素构成的
2
n
−
1
2^{n-1}
2n−1个子集加上将由集合中前
n
−
1
n-1
n−1个元素构成的
2
n
−
1
2^{n-1}
2n−1个子集中的每一个子集添加元素
a
n
−
1
a_{n-1}
an−1后生成的
2
n
−
1
2^{n-1}
2n−1个子集即可得到所有由集合中
n
n
n个元素构成的
2
n
2^n
2n个子集。这很容易从前面的将子集与一个二进制数对应起来的思想得到。相当于一个n位二进制数的前
n
−
1
n-1
n−1位都已经确定,第
n
−
1
n-1
n−1位为0或1时就可以生成两个新的n为二进制数。第
n
−
1
n-1
n−1位为0时相当于由不包含元素
a
n
−
1
a_{n-1}
an−1构成的子集,第
n
−
1
n-1
n−1位为1时相当于由包含元素
a
n
−
1
a_{n-1}
an−1构成的子集。
//开始调用时取index的值为0
vector<vector<int>> getSubSets(vector<int> set,int index)
{
vector<vector<int>> allSubSets;
if (set.size() == index)
{
allSubSets.push_back(vector<int>());
}
else
{
allSubSets = getSubSets(set, index + 1);
int item = set.at[index];
vector<vector<int>> moreSubSets;
for (vector<int> currentSubSet : allSubSets)
{
vector<int> newSubSet;
newSubSet = currentSubSet;
newSubSet.push_back(item);
moreSubSets.push_back(newSubSet);
}
allSubSets.insert(allSubSets.end(), moreSubSets.begin(), moreSubSets.end());
}
return allSubSets;
}
以下算法更直接的利用了将集合的每一个子集与一个有n个二进制位的二进制数对应起来的思想。对于0到 2 n − 1 2^{n}-1 2n−1一共 2 n 2^n 2n个二进制数,逐一求得其对应的子集,然后将它们合起来即为所求。
vector<int> convertIntToSet(int x, vector<int> set)
{
vector<int> subset;
int index = 0;
for (int k=x;k>0;k>>=1)
{
if ((k & 1) == 1)
subset.push_back(set.at(index));
index++;
}
return subset;
}
vector<vector<int>> getSubSets(vector<int> set)
{
vector<vector<int>> allSubSets;
int max = 1 << set.size();
for (int k = 0; k < max; k++)
{
vector<int> subset = convertIntToSet(k, set);
allSubSets.push_back(subset);
}
return allSubSets;
}