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)
文章参考: