给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
1 | 输入:nums = [1,2,3] |
示例 2:
1 | 输入:nums = [0] |
nums
中的所有元素互不相同
二进制枚举子集
n 为元素的集合,子集的个数(含空集)为2n,表示 2n 种状态。假设集合为nums = [1,2,3],它的子集表示如下:
子集 | 二进制 | 对应十进制 |
---|---|---|
[] | 000 | 0 |
[1] | 001 | 1 |
[2] | 010 | 2 |
[1,2] | 011 | 3 |
[3] | 100 | 4 |
[1,3] | 101 | 5 |
[2,3] | 110 | 6 |
[1,2,3] | 111 | 7 |
从 000 到 111 总共包含 23 种状态,恰好对应子集的个数。我们将对应的二进制位为 1 表示取集合中该位置元素,为 0 表示不取集合中该位置元素。例如010
,二进制位的序号为 0,1,2,下标 0 和 2为 0 ,不取,下标 1 为 1,表示取,所以该子集为 [nums[1]] = [2]。
代码如下:
1 | class Solution { |
用 1 << len 得到 2n
时间复杂度
n 个元素的集合,总共有 2n种状态,每种状态都需要遍历 n 次,总的时间复杂度为 O(n 2n)
空间复杂度
临时数组 tem 的空间,空间复杂度为 O(n)
回溯
对于 n 个元素的集合来说,我们需要对集合做 n 次选择,从左到右选择元素,对于每一个元素,要么选,要么不选,所以可以用回溯法来实现。
递归树如下:
代码如下:
1 | class Solution { |
时间复杂度
根据递归树可知,它的叶子节点个数为子集个数,叶节点个数为 2n ,构建每个子集都需要 O(n) 的时间,所以总的时间为 O(n 2n)。
空间复杂度
临时数组 tem O(n) ,递归的深度为 n,每次递归空间复杂度为 O(1)。所以总的空间复杂度为: O(n)。
进阶——存在重复元素
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
1 | 输入:nums = [1,2,2] |
示例 2:
1 | 输入:nums = [0] |
二进制枚举
给定一个集合[1,2,2],当选择元素 x ,若前面有一个相同元素 y 并没有被选取,那么包含 x 的所有子集必然会出现包含 y 的子集中。我们可以通过判断,排除存在重复的子集。
在开始枚举之前,现将 nums 数组进行排序。
代码如下:
1 | class Solution { |
时间复杂度:
排序时间复杂度为 O(n lgn),总共构造 2n 个子集,构造每个子集所需时间为 O(n),生成所有子集的时间为 O(n 2n),因为从渐进意义上来讲 O(n lgn) < O(n 2n),所以总的时间复杂度为 O(n 2n)
空间复杂度
使用了一个长度最多为 n 的临时数组,所以为 O(n)
回溯
不存在重复元素的情况类似,当选择元素 x ,若前面有一个相同元素 y 并没有被选取,那么包含 x 的所有子集必然会出现包含 y 的子集中。若当前元素为符合上述条件中的 x,则直接返回。
1 | class Solution { |
时间复杂度:
最坏情况下为不存在重复元素,排序所需时间 O(n lgn),1 个叶节点代表一个子集,叶节点的个数为 2n,构造每个子集需要 O(n),构造子集的时间为 O(n 2n),O(n lgn) < O(n 2n),时间复杂度总是忽略低阶项,所以全过程的时间复杂度为 O(n 2n)
空间复杂度
使用一个临时数组 tem,递归最深时栈空间 O(n),总的空间复杂度为 O(n)