给定一组不含重复元素的整数数组 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 }
时间复杂度分析:
外层循环执行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 }
算法复杂度分析:
每次都把当前元素添加到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层,但是每层所操作的都是同一个对象sub, 所用的空间都不是很大,这里空间复杂度不太会分析,只知道如果元素个数太多会导致栈的深度太深而导致栈溢出