力扣 78.子集

 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)

文章参考:

https://leetcode-cn.com/problems/subsets/solution/er-jin-zhi-wei-zhu-ge-mei-ju-dfssan-chong-si-lu-9c/

 

posted @ 2020-05-12 20:37  Lucky小黄人^_^  阅读(403)  评论(0编辑  收藏  举报