LeetCode 78. 子集

228 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第43天,点击查看活动详情.

78. 子集

题目描述

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

解题思路

思路一: 迭代, 利用二进制和位元算

集合的每个元素,都有可以选或不选,用二进制和位运算
一共2nums.length2^{nums.length}种组合
实现代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {
    let len = nums.length, res = [];
    for (let i = 0; i < (1 << len); i++) {
        let sub = [];
        for (let j = 0; j < len; j++)
            if (((i >> j) & 1) == 1) sub.push(nums[j]);
        res.push(sub);
    }
    return res;
}; 
  • 时间复杂度:O(n×2n)O(n \times 2^n) 一共 2n2^n个状态,每种状态需要 O(n) 的时间来构造子集
  • 空间复杂度:O(n)

思路二: 枚举

逐个枚举,空集的幂集只有空集,每增加一个元素,让之前幂集中的每个集合,追加这个元素,就是新增的子集。
循环枚举
实现代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {
    let res = [[]];
    for (let n of nums) { 
        let len = res.length;
        for (let i = 0; i < len; i++) {
            let newSub = [...res[i]];
            newSub.push(n);
            res.push(newSub);
        }
    }
    return res;
};  

思路三: 回溯

  1. 排序(升序或者降序都可以),排序是剪枝的前提 nums.sort((a, b) => a - b);
  2. 考虑重复元素,剪枝去重 if (j > i && nums[j] == nums[j - 1]) continue;

实现代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */ 
var subsets = function(nums) {
    let len = nums.length,
        res = [[]];
    nums.sort((a, b) => a - b)
    backtrack(nums, len, 0, [], res)
    return res;
}; 

function backtrack(nums, len, i, sub, res) {
    for (let j = i; j < len; j++) {
        // 剪枝去重
        if (j > i && nums[j] == nums[j - 1]) continue;
        sub.push(nums[j]);
        res.push([...sub]);
        backtrack(nums, len, j + 1, sub, res);
        // 撤销选择
        sub.pop();
    }
} 
  • 时间复杂度为: O(n×n!)O(n×n!),其中n为数组nums的长度

参考资料

二进制位,逐个枚举,DFS 三种思路,9+ 种以上的写法 - 子集 - 力扣(LeetCode)