leetcode 78 子集I(递归+回溯/位运算)

给定一组不含重复元素的整数数组 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;
}

用递归和回溯思想方法得出的结果:
在这里插入图片描述
用位运算得出的结果:
在这里插入图片描述

发布了65 篇原创文章 · 获赞 4 · 访问量 1008

猜你喜欢

转载自blog.csdn.net/CLZHIT/article/details/103681331