给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[ [3], [1],[2], [1,2,3],[1,3],[2,3],[1,2],[]]
思路:
在所有子集中,生成各个子集,即是否选择[1],是否选择[2],是否选择[3]的问题
如果只使用循环,困难的地方在哪里?
使用循环难以直接模拟是否选某一元素的过程
如果只是生成[1]、[1,2]、[1,2,3]三个子集,如何做?
#include<stdio.h>
#include<vector>
int main()
{
vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
vector<int> item; //生成各个子集的数组
vector<vector<int>> result; //最终数组
for (int i = 0; i < nums.size(); i++) {
item.push_back(nums[i]);
result.push_back(item);
}
for (int i = 0; i < result.size(); i++){
for (int j = 0; j < result[i].size(); j++) {
printf("[%d]", result[i][j]);
}
printf("\n");
}
return 0;
}
用递归实现上述的代码
#include<stdio.h>
#include<vector>
void generate(int i, vector<int>& nums, vector<int>& item, vector<vector<int>>& result)
{
if (i >= nums.size()) { //递归条件结束,当下标i超过nums数组长度时,递归结束
return;
}
item.push_back(nums[i]);
result.push_back(item);
generate(i+1, nums, item, result);
}
int main()
{
vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
vector<int> item; //生成各个子集的数组
vector<vector<int>> result; //最终数组
generate(0, nums, item, result);
for (int i = 0; i < result.size(); i++){
for (int j = 0; j < result[i].size(); j++) {
printf("[%d]", result[i][j]);
}
printf("\n");
}
return 0;
}
利用回溯方法生成子集,即对于每个元素,都有试探放入或不放入集合中的两个选择:
选择放入该元素,递归的进行后续元素的选择,完成放入该元素后序所有元素的试探;之后将其拿出,即再进行一次选择不放入该元素,递归的进行后续元素的选择,完成不放入该元素后续所有元素的试探。
本来选择放入,再选择一次不放入的这个过程,称为回溯试探法
例如:
元素数组:nums = [1,2,3,4,5……],子集生成数组item[] = []
对于元素1,
选择放入item, item = [1], 继续递归处理后续[2,3,4,5,……]元素;item = [1,……]
选择不放入item, item = [], 继续递归处理后续[2,3,4,5,……]元素;item = [……]
红色部分是第一次递归调用,而蓝色的部分是pop()出元素后的第二次递归调用
复杂度为O(2^n)
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result; //存储最终结果的result
vector<int> item; //回溯时产生各个子集的数组
result.push_back(item); //将空集push进入result
generate(0, nums, item, result); //计算各个子集
return result;
}
private:
void generate(int i, vector<int>& nums, vector<int> &item, vector<vector<int>> &result){
if(i >= nums.size()){ //递归结束条件
return;
}
item.push_back(nums[i]);
result.push_back(item); //将当前生成的子集添加进入result
generate(i+1, nums, item, result); //第一次递归调用
item.pop_back();
generate(i+1, nums, item, result); //第一次递归调用
}
};
另一种方法,采用位运算的方式,用到按位与&和左移<<(乘2)这两种位运算
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
int all_set = 1 << nums.size(); // 1 << n, 即为2^n 总共集合中组成元素的数目
for(int i = 0; i < all_set; i++){ //外层循环循环那些存在的集合
vector<int> item;
for(int j = 0; j < nums.size(); j++){
if(i & (1 << j)){ //构造数字i代表的集合,各元素存储至item
item.push_back(nums[j]); //参考上面图片解析
}
}
result.push_back(item);
}
return result;
}
};
//整数i代表了从0至2^n-1这2^n个集合
//(1 << j)即为构造nums数组的第j个元素
//若i & (1 << j)为真则nums[j]放入item
测试代码:
int main()
{
vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
vector<vector<int>> result; //最终数组
Solution solve;
result = solve.subsets(nums);
for (int i = 0; i < result.size(); i++){
if (result[i].size() == 0) {
printf("[]");
}
for (int j = 0; j < result[i].size(); j++) {
printf("[%d]", result[i][j]);
}
printf("\n");
}
return 0;
}
用递归和回溯思想方法得出的结果:
用位运算得出的结果: