给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
解法一:
这道求子集合的问题,由于其要列出所有结果,按照以往的经验,肯定要是要用递归来做。这道题其实它的非递归解法相对来说更简单一点,下面我们先来看非递归的解法,我们可以一位一位的往上叠加,比如对于题目中给的例子 [1,2,3] 来说,最开始是空集,那么我们现在要处理 1,就在空集上加 1,为 [1],现在我们有两个子集 [] 和 [1],下面我们来处理 2,我们在之前的子集基础上,每个都加个 2,可以分别得到 [2],[1, 2],那么现在所有的子集合为 [],[1],[2],[1, 2],同理处理 3 的情况可得 [3],[1, 3],[2, 3],[1, 2, 3],再加上之前的子集就是所有的子集合了,代码如下:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
lists.add(new ArrayList<>());
for (int i = 0; i < nums.length; i++) {
int size = lists.size();
for (int j = 0; j < size; j++) {
List<Integer> list = lists.get(j);
list.add(nums[i]);
lists.add(new ArrayList<>(list));
//System.out.println("before: " + list);
list.remove(list.size() - 1);
//System.out.println("after: " + list);
}
}
return lists;
}
打印迭代过程如下:
before: [1]
after: []
before: [2]
after: []
before: [1, 2]
after: [1]
before: [3]
after: []
before: [1, 3]
after: [1]
before: [2, 3]
after: [2]
before: [1, 2, 3]
after: [1, 2]
解法二
下面来看递归的解法,这相当于一种深度优先搜索,参见网友JustDoIt 的博客,由于原集合每一个数字只有两种状态,要么存在,要么不存在,那么在构造子集时就有选择和不选择两种情况,所以可以构造一棵二叉树,左子树表示选择该层处理的节点,右子树表示不选择,最终的叶节点就是所有子集合,树的结构如下:
[]
/ \
/ \
/ \
[1] []
/ \ / \
/ \ / \
[1 2] [1] [2] []
/ \ / \ / \ / \
[1 2 3] [1 2] [1 3] [1] [2 3] [2] [3] []
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> out = new ArrayList<>();
subsetsDFS(nums, 0, out, lists);
return lists;
}
public void subsetsDFS(int[] nums, int level, List<Integer> out, List<List<Integer>> lists) {
lists.add(out);
for (int i = level; i < nums.length; i++) {
out.add(nums[i]);
List<Integer> list = new ArrayList<>(out);
subsetsDFS(nums, i + 1, list, lists);
out.remove(out.size() - 1);
}
}
子集添加的顺序如下:
[]
[1]
[1, 2]
[1, 2, 3]
[1, 3]
[2]
[2, 3]
[3]
解法三
最后我们再来看一种解法,这种解法是 CareerCup 书上给的一种解法,想法也比较巧妙,把数组中所有的数分配一个状态,true 表示这个数在子集中出现,false 表示在子集中不出现,那么对于一个长度为 n 的数组,每个数字都有出现与不出现两种情况,所以共有 2^n 种情况,我们把每种情况都转换出来就是子集了,我们还是用题目中的例子, [1 2 3] 这个数组共有 8 个子集,每个子集的序号用二进制表示,把是 1 的位对应原数组中的数字取出来就是一个子集,八种情况都取出来就是所有的子集了,参见代码如下:
1 | 2 | 3 | Subset | |
---|---|---|---|---|
0 | F | F | F | [] |
1 | F | F | T | [3] |
2 | F | T | F | [2] |
3 | F | T | T | [2, 3] |
4 | T | F | F | [1] |
5 | T | F | T | [1, 3] |
6 | T | T | F | [1, 2] |
7 | T | T | T | [1, 2, 3] |
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
int max = 1 << nums.length;
for (int i = 0; i < max; i++) {
List<Integer> list = convertIntToSet(nums, i);
lists.add(list);
}
return lists;
}
private List<Integer> convertIntToSet(int[] nums, int i) {
List<Integer> sub = new ArrayList<>();
int idx = 0;
for (int k = i; k > 0; k >>= 1) {
if ((k & 1) == 1) {
sub.add(nums[idx]);
}
idx++;
}
return sub;
}
本文参考自 [LeetCode] Subsets 子集合