算法力扣刷题记录 七十五【78.子集】

前言

回溯章节第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. 题目要求一个集合的所有子集。相当于从一个集合中选择元素重新组成新集合,新集合是原集合的子集。约等于组合问题。应该用回溯算法暴力搜索

思路

  1. 题目说集合中的元素互不相同,没有重复的元素。那么就不用考虑重复元素带来的影响,可以直接选。
  2. 但是子集中元素的数量也是一个循环。子集内元素数量是1个?2个?3个?……,这个循环应该放到哪里?(先放着)
  3. 先搜索子集元素数量为0;再搜索子集元素数量为1;再搜索子集元素数量为2;……
  4. 回溯三部曲:
  • 确定递归的返回值:用全局变量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]弹出。
  1. 所以递归函数是搜索指定子集大小的情况,那么子集数量的循环应该在主函数中。
  • 主函数中指定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;
    }
};

三、参考学习

【78.子集】参考学习链接

学习内容

  1. 总结了组合问题字符串切割问题它们都是树形结构的叶子节点搜集结果。而本文子集问题要搜集树上的所有节点
  2. 确定子集问题的树形结构,用参考给出的图:
    在这里插入图片描述
  3. 每一层递归都需要搜集结果,而不是在终止条件搜集结果。所以整个递归函数就可以获得所有子集大小,不需要在主函数中有for循环,不用count参数了
  4. 代码实现:
  • 无需终止条件,进到递归函数中,先放入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;
    }
};
  1. 分析下时间复杂度:O(递归次数 * 每次递归的时间复杂度)。每次递归都要走一个for循环,所以for循环的时间跟n有关。递归次数:nums大小是n,那么递归深度是n+1,那么树节点最多是2n+1 -1。化简之后时间复杂度是O(n* 2n)。所以提示中n <=10,不是很大。
  2. 分析下空间复杂度:O(递归深数 * 每次递归的空间复杂度)。每次递归都是使用同一个temp,没有开辟新的空间,所以O(1)。递归深数是n+1,所以空间复杂度是O(n)。

总结

在这里插入图片描述
(欢迎指正,转载标明出处)

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值