LeetCode 78. Subsets
暑假我在leetcode上已经做了200多道题,虽然其中大部分是简单题,但是还是有一些题目令我做到脱发,因此,我想挑几道我暑期收藏的题目,记录下解题过程。
目录:
- 问题描述
- 解法一
- 解法二
问题描述
解法一
这道题我们并不陌生,就是求一个给定集合的子集。通常,我们是根据元素个数来求子集的,即首先找出元素个数为0的子集,接着找出所有元素个数为1的子集,以此类推,直到找到元素个数与给定集合相等的集合。
解法一便是根据这个思路写出的。
/*首先,空集是所有集合的子集,因此可以直接在结果中加入空集:*/
vector<vector<int>> res;
res.push_back({});
for(int i = 1; i <= nums.size(); i++) {
/*每次循环,找出所有元素个数为n的子集*/
int n = i;
/*用队列q来装载这次循环得到的子集*/
queue<vector<int>> q;
/*由于n>=1,因此可以先找出所有元素个数为1的子集,
再在此基础上添加其他元素*/
for(int j = 0; j < nums.size(); j++) {
q.push({nums[j]});
}
/*当q不为空,即还未找全元素个数为n的子集时,
依次给q中的每个集合添加一个可能的新元素*/
while(!q.empty()) {
int size = q.size();
while(size--) {
vector<int> temp = q.front();
q.pop();
if(temp.size() != n) {
int k = 0;
//找到temp最后一个元素在nums中的位置
for(; k < nums.size(); k++) {
if(nums[k] == temp[temp.size() - 1]) {
break;
}
}
//从剩余元素中挑选元素,依次与temp组成新子集
if(nums.size() - k - 1 >= n - temp.size()) {
k++;
for(; k < nums.size(); k++) {
vector<int> temp2 = temp;
temp2.push_back(nums[k]);
if(temp2.size() == n) res.push_back(temp2);
else q.push(temp2);
}
}
}
/*当n为1时,可以直接将此子集加入结果*/
else res.push_back(temp);
}
}
}
return res;
解法二
第一种解法虽然思路简单,但是复杂度挺高的,嵌套了4层循环,而且队列也容易造成空间复杂度过高。换一种思路,我们可以发现,当我们已经求出了集合A的子集,而集合B只比集合A多一个数,那么,集合B的子集就等于集合A的每个子集加上多出来的这个数,再并上集合A的子集,例如:
已知集合 [ 1 ] 的子集为{ }, {1},那么集合 [1,2] 的子集就是集合 [ 1 ] 的每个子集加上2:{2}, {1,2},再并上集合 [ 1 ] 的子集:{ }, {1},即{ }, {1},{2}, {1,2};
同理,集合 [1,2,3] 的子集就是{ { 3}, {1,3},{2,3}, {1,2,3} }∪{ { }, {1},{2}, {1,2} } = { { }, {1},{2}, {1,2}, {3}, {1,3},{2,3}, {1,2,3} }
根据这种思路,我们可以写出解法二:
vector<vector<int>> res;
/*每次循环可以求出nums前i+1项的子集*/
for(int i = 0; i < nums.size(); i++) {
if(i == 0) res.push_back({});
int size = res.size();
//给当前的每个子集加上一个数
for(int j = 0; j < size; j++) {
res.push_back(res[j]);
res.back().push_back(nums[i]);
}
}
return res;
解法二不仅代码量比解法一少了很多,复杂度也下降了,不过当给定数组比较长时,leetcode会超出输出限制,因此没能测试更多用例。不过,这两个算法都能通过leetcode当前的测试样例。