与combination/combination sum I, II思路一样。区别在于单层扫描时不用跳过重复数字,而在进入下一层递归前就需要把当前subset压入结集中。主要递推关系就是假设函数返回递归集合,现在加入一个新的数字,我们如何得到包含新数字的所有子集。其实就是在原有的集合中对每集合中的每个元素都加入新元素得到子集,然后放入原有集合中(原来的集合中的元素不用删除,因为他们也是合法子集)。而结束条件就是如果没有元素就返回空集(注意空集不是null,而是没有元素的数组)就可以了。时间和空间都是取决于结果的数量,也就是O(2^n)
//No-recursive:Given a set S of n distinct integers,
//there is a relation between Sn and Sn-1.
//The subset of Sn-1 is the union of {subset of Sn-1} and {each element in Sn-1 + one more element}.
//Therefore, a Java solution can be quickly formalized.
public ArrayList<ArrayList<Integer>> subsets(int[] nums) {
ArrayList<ArrayList<Integer>> result=new ArrayList<ArrayList<Integer>>();
//ArrayList<Integer> list=new ArrayList<Integer>();
if(nums==null || nums.length==0){
return result;
}
Arrays.sort(nums);
for(int i=0;i<nums.length;i++)
{
ArrayList<ArrayList<Integer>> temp=new ArrayList<ArrayList<Integer>>();
//get sets that are already in result
for(ArrayList<Integer> a:result)
{
temp.add(new ArrayList<Integer>(a));//store and copy
}
//add nums[i] to existing sets
for(ArrayList<Integer> b:temp)
{
b.add(nums[i]);
}
//add nums[i] only as a set
ArrayList<Integer> single=new ArrayList<Integer>();
single.add(nums[i]);
temp.add(single);
//every loop temp is a set of all new added
result.addAll(temp);
}
result.add(new ArrayList<Integer>());// add an empty set
return result;
}
这就是一个典型的递归结构嘛,[1,2,3] 的子集可以由 [1,2] 追加得出,[1,2] 的子集可以由 [1] 追加得出,base case 显然就是当输入集合为空集时,输出子集也就是一个空集。
翻译成代码就很容易理解了:
vector<vector<int>> subsets(vector<int>& nums) { // base case,返回一个空集 if (nums.empty()) return {{}}; // 把最后一个元素拿出来 int n = nums.back(); nums.pop_back(); // 先递归算出前面元素的所有子集 vector<vector<int>> res = subsets(nums); int size = res.size(); for (int i = 0; i < size; i++) { // 然后在之前的结果之上追加 res.push_back(res[i]); res.back().push_back(n); } return res;}
这个问题的时间复杂度计算比较容易坑人。我们之前说的计算递归算法时间复杂度的方法,是找到递归深度,然后乘以每次递归中迭代的次数。对于这个问题,递归深度显然是 N,但我们发现每次递归 for 循环的迭代次数取决于 res 的长度,并不是固定的。
根据刚才的思路,res 的长度应该是每次递归都翻倍,所以说总的迭代次数应该是 2^N。或者不用这么麻烦,你想想一个大小为 N 的集合的子集总共有几个?2^N 个对吧,所以说至少要对 res 添加 2^N 次元素。
那么算法的时间复杂度就是 O(2^N) 吗?还是不对,2^N 个子集是 push_back 添加进 res 的,所以要考虑 push_back 这个操作的效率:
vector<vector<int>> res = ...for (int i = 0; i < size; i++) { res.push_back(res[i]); // O(N) res.back().push_back(n); // O(1)}