【leetcode】78 子集(回溯,组合)

题目链接:https://leetcode-cn.com/problems/subsets/

题目描述

思路

1 回溯-辅助数组记录是否取某个位置元素

复杂度分析
时间复杂度:O(2^n)
空间复杂度:O(n)

/*
 * 回溯算法
 * 类似0-1背包,每个位置元素有选和不选两种选择,组合数共2^n种
 *
 */
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ret;
        if(nums.empty()) return ret;
        vector<bool > take(nums.size(), false); // 辅助数组用来标记该位置元素是否选该位置元素
        subsetsCore(nums,0,take,ret);
        return ret;
    }
    void subsetsCore(vector<int> &nums, int begin, vector<bool> &take, vector<vector<int>> &ret){
        // 当位置到达末尾递归结束
        if (begin >= nums.size()){
            vector<int> tmp;
            for (int i = 0; i < nums.size(); ++i) {
                if(take[i])                     // 取take数组中值为true位置对应的元素
                    tmp.push_back(nums[i]);
            }
            ret.push_back(tmp);
            return;
        }
        take[begin] = true;
        subsetsCore(nums,begin+1, take, ret);   // 递归到下一元素
        take[begin] = false;
        subsetsCore(nums,begin+1, take, ret);
    }

};

【leetcode】78 子集(回溯,组合)_第1张图片

2 回溯-位运算优化

我们知道,对于给定一个集合里,所有元素的集合它们应该满足这样一个公式: 假设所有的组合数之和为sum,则有sum = C(n, 0) + C(n, 1) + …+ C(n, n); 分别对应取集合中的一个元素,两个元素…n个元素。而通过数学公式二项式定义,这个和是等于2 ** n(2的n次方)。就是说,我们所有取的组合数为一个指数函数。

假设输入是1、2、3。

首先全部的子集为【000】【001】【010】【100】【011】【101】【110】【111】 1表示这一位的数字存在,例如 【010】 表示只含有 2

由此发现子集所代表的二进制数全部小于 1 << 数组.length 第一层循环 for (int i = 0; i < (1 << size); i++)

然后根据【i】 的二进制数中 【1】 的位置取得子集

/*
 * 回溯算法优化
 * 集合的每个元素,都有可以选或不选,用二进制和位运算,可以很好的表示。
 * 时间复杂度O(2^N) 空间复杂度O(n)
 */
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ret;
        if(nums.empty()) return ret;
        for (int i = 0; i <  1<<nums.size() ; ++i) {
            vector<int> tmp;
            for (int j = 0; j < nums.size(); ++j) {
                if((i >> j) & 1 == 1)
                    tmp.push_back(nums[j]);
            }
            ret.push_back(tmp);
        }

        return ret;
    }
};

【leetcode】78 子集(回溯,组合)_第2张图片
将内层循环的终止条件换为 j < int(log2(i))+1 进一步缩短时间
在这里插入图片描述

3 迭代

逐个枚举,空集的幂集只有空集,每增加一个元素,让之前幂集中的每个集合,追加这个元素,就是新增的子集。

(1)初始化ret为空集
(2)遍历nums数组:
遍历已有的ret数组:
保留原ret数组内容,并以原re所有列表后加上num作为新列表添加到ret数组

/*
 * 迭代
 * 时间复杂度O(2^N) 空间复杂度O(1)
 */
class SolutionI {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ret;
        if(nums.empty()) return ret;
        ret.push_back(vector<int> {});
        for (auto num:nums){
            int len = ret.size();	
            for (int i = 0; i < len; ++i) { // 注意需要取当前的长度不能用ret.size(),否则会陷入死循环
                vector<int> tmp = ret[i];
                tmp.push_back(num);
                ret.push_back(tmp);
            }
        }
        return ret;
    }
};

【leetcode】78 子集(回溯,组合)_第3张图片

你可能感兴趣的:(LeetCode)