Files
leetcode-master/problems/0078.子集.md
youngyangyang04 c761b59b50 Update
2020-09-18 16:51:32 +08:00

3.2 KiB
Raw Blame History

题目地址

第78题. 子集

给定一组不含重复元素的整数数组 nums返回该数组所有可能的子集幂集

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

思路

求子集问题和 求组合组合和分割问题又不一样了, 如何把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是找树的叶子节点,而子集问题是找树的所有节点!

取子集也是,其实也是一种组合位置,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。 那么既然是无序写回溯算法的时候for就要从startIndex开始而不是从0开始

那有同学问题什么时候for可以从0开始求排列问题的时候就要从0开始因为集合是有序的{1, 2} 和{2, 1}是两个集合。

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:

从图中,可以看出,遍历这个树的时候,把所有节点都记录下来,就是要求的子集。

来看一下我总结的回溯模板来:

backtracking() {
    if (终止条件) {
        存放结果;
    }

    for (选择:选择列表(可以想成树中节点孩子的数量)) {
        递归,处理节点;
        backtracking();
        回溯,撤销处理结果
    }
}

首先是终止条件终止条件就是startIndex已经大于数组的长度了就是终止了代码如下:

if (startIndex >= nums.size()) {
    return;
}

但是,要明确的是,求取子集问题,其实没有必要加终止条件,因为子集就是要遍历整个一棵树,不需要任何剪枝!

大家一会看到下面整体代码的时候就知道了。

然后就是看如何写for循环因为求子集也是无序的所以for循环要从startIndex开始

代码如下:

for (int i = startIndex; i < nums.size(); i++) {

接下来就是递归与回溯,定一个vector<int> path用来收集子集的元素在回溯的时候还要弹出backtracking每次调用自己的时候记着要从i+1 开始,代码如下:

for (int i = startIndex; i < nums.size(); i++) {
    path.push_back(nums[i]);
    backtracking(nums, i + 1);
    path.pop_back();
}

重点代码分析完之后,整体代码如下:

可以发现我在backtracking里并没有写终止条件因为本来我们就要遍历整颗树。

有的同学可能担心会不会无限递归? 并不会因为每次递归的下一层就是从i+1开始的。

C++代码

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};