78. 子集
题目描述
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
法一:利用二进制的位运算
思路:
利用位数为数组长度的二进制数,这个二进制数所能表示的元素个数刚好等于这个幂集的子集的个数,且二进制数的每个数代表了一种子集的选择情况,数位为1则表示选该数,数位0则表示不选该数
1 class Solution {
2 public List<List<Integer>> subsets(int[] nums) {
3 // 利用位数为数组长度的二进制数,这个二进制数所能表示的元素个数刚好等于这个幂集的子集的个数
4 // 且二进制数的每个数代表了一种子集的选择情况,数位为1则表示选该数,数位0则表示不选该数
5 ArrayList<List<Integer>> list = new ArrayList<List<Integer>>();
6 for(int i = 0; i < (1 << nums.length); i++){
7 List<Integer> sub = new ArrayList<Integer>();
8 for(int j = 0; j < nums.length; j++){
9 if(((i >> j) & 1) == 1)
10 sub.add(nums[j]);
11 }
12 list.add(sub);
13 }
14 return list;
15 }
16 }
leetcode执行用时:1 ms > 99.39%, 内存消耗:38.9 MB > 78.18%
时间复杂度分析:
外层循环执行2^n次,内层循环每轮执行n次,所以时间复杂度为 O(N*2^N)
法二:枚举法
思路:
枚举每个元素,把这个元素添加到现有的所有集合,然后把这个新集合加入list中
1 class Solution {
2 public List<List<Integer>> subsets(int[] nums) {
3 // 枚举每个元素,把这个元素添加到现有的所有集合,然后把这个新集合加入list中
4 ArrayList<List<Integer>> list = new ArrayList<List<Integer>>();
5 list.add(new ArrayList()); // 添加一个空集合
6 for(int i = 0; i < nums.length; i++){
7 int size = list.size();
8 for(int j = 0; j < size; j++){
9 List<Integer> sub = new ArrayList<>(list.get(j));
10 sub.add(nums[i]);
11 list.add(sub);
12 }
13 }
14 return list;
15 }
16 }
leetcode执行用时:2 ms > 26.61%, 内存消耗:39.3 MB > 20.18%
算法复杂度分析:
时间复杂度:每次都把当前元素添加到list的所有集合中,所以每轮下来list中集合数量可以加倍,所以总的时间复杂度为O(1 + 2^1 + 2^2 +....+ 2^(n-1)) = O(2^n - 1) = O(2^n),可以看出这个算法的效率比第一种方法要高
法三:(回溯法)
思路:
回溯法,对每个元素进行选与不选的判断
1 class Solution {
2 public List<List<Integer>> subsets(int[] nums) {
3 // 回溯法,对每个元素进行选与不选的判断
4 ArrayList<List<Integer>> list = new ArrayList<List<Integer>>();
5 ArrayList<Integer> sub = new ArrayList<Integer>();
6 traceBack(nums, 0, list, sub);
7 return list;
8 }
9 // i表示元素的下标,sub用来存储一个子集
10 public void traceBack(int[] nums, int i, ArrayList<List<Integer>> list, ArrayList<Integer> sub){
11 if(i >= nums.length){
12 list.add(new ArrayList<Integer>(sub));
13 return;
14 }
15 // 不选当前元素
16 traceBack(nums, i + 1, list, sub);
17
18 // 选当前元素
19 sub.add(nums[i]);
20 traceBack(nums, i + 1, list, sub);
21 sub.remove(sub.size() - 1); // 回溯到添加元素前的状态
22 }
23 }
复杂度分析:
时间复杂度:每个元素都有选与不选两种情况,所以时间复杂度为 O(2^n)
空间复杂度:如果不考虑结果集的空间花费,递归栈的最大深度为 n层,所以这里有一个O(n)的空间花费,另外每层都会操作同一个对象sub, 往这个集合中添加或删除元素,所以这个sub的空间也是一个空间开销,sub最大为O(n), 所以该算法的空间复杂度为O(2n)