前言
回溯章节第8篇。记录 七十五【78.子集】
一、题目阅读
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同
二、尝试解答
分析题目,确定方法
- 题目要求一个集合的所有子集。相当于从一个集合中选择元素重新组成新集合,新集合是原集合的子集。约等于组合问题。应该用回溯算法暴力搜索。
思路
- 题目说集合中的元素互不相同,没有重复的元素。那么就不用考虑重复元素带来的影响,可以直接选。
- 但是子集中元素的数量也是一个循环。子集内元素数量是1个?2个?3个?……,这个循环应该放到哪里?(先放着)
- 先搜索子集元素数量为0;再搜索子集元素数量为1;再搜索子集元素数量为2;……
- 回溯三部曲:
- 确定递归的返回值:用全局变量vector< vector< int >> result来放结果,所以不需要返回值。void。
- 确定递归的参数:
- int startindex 表示该层从哪个下标开始,后面的元素可以被选择。因为上面层中选过的元素不应该重复被选;
- vector < int>& nums,输入原始参数;
- int count本次搜索的子集内元素数量。(可能不是一开始确定,在确定终止条件后回头来补充)
- 确定终止条件:当这个子集内的元素数量是本次指定的子集大小,就应该停止。(回去补充一个参数count) if(temp.size() == count) 搜集结果,return。
- 单层逻辑:
- for循环起始从startindex开始,直到< nums.size();
- 把nums[i]放入temp中;
- 递到下一层:startindex是i+1,count不变。
- 回溯:把nums[i]弹出。
- 所以递归函数是搜索指定子集大小的情况,那么子集数量的循环应该在主函数中。
- 主函数中指定count=0,调用backtracking;
- 主函数中指定count=1,再调用backtracking;
- 主函数中指定count=2,再调用backtracking;……
代码实现
class Solution {
public:
vector<vector<int>> result;
vector<int> temp;
void backtracking(int startindex,int count,vector<int>& nums){
if(temp.size() == count){
result.push_back(temp);
return;
}
for(int j = startindex;j < nums.size();j++){
temp.push_back(nums[j]);
backtracking(j+1,count,nums);
temp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
temp.clear();
for(int i = 0;i <= nums.size();i++){
backtracking(0,i,nums);
}
return result;
}
};
三、参考学习
学习内容
- 总结了组合问题和 字符串切割问题它们都是树形结构的叶子节点搜集结果。而本文子集问题要搜集树上的所有节点。
- 确定子集问题的树形结构,用参考给出的图:
- 每一层递归都需要搜集结果,而不是在终止条件搜集结果。所以整个递归函数就可以获得所有子集大小,不需要在主函数中有for循环,不用count参数了。
- 代码实现:
- 无需终止条件,进到递归函数中,先放入temp。再去遍历。
- 终止条件,其实是for循环i < nums.size()控制。当本层的startindex == nums.size()时,就会return。
- 或者认为终止条件是 if(startindex >= nums.size()) return;
class Solution {
public:
vector<vector<int>> result;
vector<int> temp;
void backtracking(int startindex,vector<int>& nums){
result.push_back(temp);
for(int i = startindex;i < nums.size();i++){
temp.push_back(nums[i]);
backtracking(i+1,nums);
temp.pop_back();
}
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
temp.clear();
backtracking(0,nums);
return result;
}
};
- 分析下时间复杂度:O(递归次数 * 每次递归的时间复杂度)。每次递归都要走一个for循环,所以for循环的时间跟n有关。递归次数:nums大小是n,那么递归深度是n+1,那么树节点最多是2n+1 -1。化简之后时间复杂度是O(n* 2n)。所以提示中n <=10,不是很大。
- 分析下空间复杂度:O(递归深数 * 每次递归的空间复杂度)。每次递归都是使用同一个temp,没有开辟新的空间,所以O(1)。递归深数是n+1,所以空间复杂度是O(n)。
总结
(欢迎指正,转载标明出处)