题目描述
想法
由于数组元素是不重复的,假设数组长度为n,那么子集总数应该是
2
n
2^{n}
2n个,且子集的大小为0,1,2…n个,数量分别为Cin,i为子集大小,我们可以采用减治的方法来解决这个问题,以一个普通的输入为例
输入nums = {x1,x2,x3.....xn-1,xn}
输入大小为n,
那么这个输入数组的所有子集可以分成
nums1 = {x1,x2,x3....xn-1}
的所有子集 再加上 它的所有子集中再添加上xn
这个元素之后的结果
将问题规模不断缩减,直到待求的数组的长度为0时,它的子集为[]
递归
上面的想法可以使用递归的方式实现,具体为:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
return getSubsets(nums,nums.length-1);
}
public List<List<Integer>> getSubsets(int[] nums, int end) {
List<List<Integer>> res = null;
if(end < 0) {
res = new ArrayList<>();
res.add(new ArrayList<Integer>());
}else{
List<List<Integer>> son = getSubsets(nums, end-1);
int size = son.size();
for(int i = 0; i < size; ++i) {
List<Integer> temp = new ArrayList<>(son.get(i));
temp.add(nums[end]);
son.add(temp);
}
res = son;
}
return res;
}
}
简化
上述代码从输入数组尾部开始递归,但实际上从计算出最小子问题的结果时才会计算更长的,所以也可以简化为:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<Integer>());
for(int num: nums) {
int size = res.size();
for(int i = 0; i < size; ++i) {
List<Integer> temp = new ArrayList(res.get(i));
temp.add(num);
res.add(temp);
}
}
return res;
}
}
思路其实是一致的,两种方法都需要注意,再遍历子问题的结果集时,必须先获取大小再利用下标进行访问,不能使用迭代器或foreach循环,原因是我们在访问时对这个结果集进行了更新,会触发ArrayList的快速失败机制而使得程序不正常退出。
两种方法的时间复杂度均为 O ( N 2 N ) O(N2^{N}) O(N2N),可以通过 T ( N ) = T ( N − 1 ) + 2 N − 1 T(N)=T(N-1)+2^{N-1} T(N)=T(N−1)+2N−1求解。
新的思路
在看别人的解法时,发现一种很有趣的思路,即采用掩码,如图:
即创建一个与输入数组同样长度的二进制数作为掩码,当他在全0~全1范围内变化的时候,就可以得到它的唯一子集。
输入数组为N位,二进制数变化范围为 2 N 2^{N} 2N,每一次变化都需要遍历一次数组从掩码为1的位置获取子集,长度为N,所以时间复杂度为 O ( N 2 N ) O(N2^{N}) O(N2N)