描述
给定一个含不同整数的集合,返回其所有的子集
注意事项
子集中的元素排列必须是非降序的,解集必须不包含重复的子集
举例
如果 S = [1,2,3],有如下的解:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
挑战
你可以同时用递归与非递归的方式解决么?
PS
这个输出顺序不符合程序逻辑顺序,助教说可能在网站后台重新排序了
逻辑顺序应该是:
[],
[1],
[1, 2],
[1, 2, 3],
[1, 3],
[2],
[2, 3]
思路
区分 i 和 startIndex
代码
- 递归:时间复杂度O(n * 2^n)
// 递归:实现方式,一种实现DFS算法的一种方式
class Solution {
/**
* @param S: A set of numbers.
* @return: A list of lists. All valid subsets.
*/
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> results = new ArrayList<>();
if (nums == null) {
return results;
}
if (nums.length == 0) {
// 这么写 return results.add(new ArrayList<Integer>()); 会返回布尔类型报错
results.add(new ArrayList<Integer>());
return results;
}
// 排序,有序
Arrays.sort(nums);
helper(new ArrayList<Integer>(), nums, 0, results);
return results;
}
// dfs
// 递归三要素
// 1. 递归的定义:把所有以 subset 开头的的集合放到 results
private void helper(ArrayList<Integer> subset,
int[] nums,
int startIndex,
List<List<Integer>> results) {
// 2. 递归的拆解
/* deep copy,Java中变量存储的其实是引用,
* 深拷贝的目的防止每次调用递归时引用指向的数据的变化影响输出结果
* results.add(subset);这种写法错的
*/
results.add(new ArrayList<Integer>(subset));
for (int i = startIndex; i < nums.length; i++) {
// [1] -> [1,2]
subset.add(nums[i]);
// 寻找所有以 [1,2] 开头的集合,并扔到 results
helper(subset, nums, i + 1, results);
/* [1,2] -> [1] 回溯
* 回溯法
* 其实remove是和add相对应的
* 每进入一层递归往list中添加一个数
* 每退出一层递归从list里减去一个数
* 本题递归流程:
* [] -> [1] -> [1, 2] -> [1, 2, 3] 没办法往下走了
* [1, 2, 3] 退出一层递归变为 [1, 2]再退出一层递归变为[1]
* [1]可以继续添加3变为[1, 3]没办法继续往下走了
* [1, 3] 删除3变为[1],删除1变为空[]
* 把2加入[]寻找以2开头的子集,[] -> [2] -> [2, 3] 没办法继续往下走了
* 从[2, 3]中删除3变为[2],然后删除2变为[]
* 把3加入[]得到所有解
*/
subset.remove(subset.size() - 1);
}
// 3. 递归的出口(本题不明显,执行到最后自动就退出了)
// return;
}
}
- 非递归:利用二进制的方式逐个枚举 subsets
class Solution {
/**
* @param S: A set of numbers.
* @return: A list of lists. All valid subsets.
*/
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int n = nums.length;
Arrays.sort(nums);
// 1 << n is 2^n
// each subset equals to an binary integer between 0 .. 2^n - 1
// 0 -> 000 -> []
// 1 -> 001 -> [1]
// 2 -> 010 -> [2]
// ..
// 7 -> 111 -> [1,2,3]
for (int i = 0; i < (1 << n); i++) {
List<Integer> subset = new ArrayList<Integer>();
for (int j = 0; j < n; j++) {
/* 将i的二进制位中的每一个1分别找出来,
* 也就得到了二进制位对应的子集
* j 从 0 到 n 对应 i 的每一位
*/
if ((i & (1 << j)) != 0) {
subset.add(nums[j]);
}
}
result.add(subset);
}
return result;
}
}