QUESTION
Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note
- All numbers (including target) will be positive integers.
- The solution set must not contain duplicate combinations.For example, given candidate set [2, 3, 6, 7] and target 7,
A solution set is:
[ [7], [2, 2, 3] ]
THOUGHTS
这道题完完全全是自己做出来的,略开心啊。不过还是调试了几次,利用的是回溯法加上dfs,这种做法应该很熟悉了,上课看书是理解不了的,只有多做题才能熟练掌握,不是提倡题海战术,只不过要多做题多思考多总结。给出一列数组,还有一个目标值,求所有和为目标值的集合。其实dfs就是一棵树啊,回溯只不过是借助这种结构进行剪枝。
CODE
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(candidates == null || candidates.length == 0)
return res;
List<Integer> temp = new ArrayList<Integer>();
Arrays.sort(candidates);
dfs(candidates,target,res,temp,0,0);
return res;
}
public void dfs(int[] nums,int target,List<List<Integer>> res,List<Integer> temp,int sum,int start){
if(sum == target){
sum = 0;
res.add(new ArrayList<Integer>(temp));
return;
}
else if(sum > target){
sum = 0;
return;
}
for(int i = start;i < nums.length;i++){
temp.add(nums[i]);
sum += nums[i];
dfs(nums,target,res,temp,sum,i);
sum -= nums[i];
temp.remove(temp.size() - 1);
while(i < nums.length - 1 && nums[i] == nums[i + 1])
i++;
}
}
}
RESULT
runtime complexity is O(n^2),space complexity is O(1).
#
SPECIAL ATTENTION
- 在进行处理之前要先排序,因为可能会出现[3,2,2]的情况,答案给出的是[2,2,3]。
- 排序之后,也要考虑结果重复的情况,比如candidates[] = {2,2,3},target = 5;那么处出现[2,3],[2,3]俩个结果,最好是在处理的时候跳过这种情况。方法就是如果下一个处理数和本次处理数相同,则跳过。
- 还要考虑出现的一种情况是,结果中包含[2,2,3],[2,3,2],[3,2,2]为什么我们从小到大排序了还会出现这种情况呢?这是因为我们进行下次递归的时候不是从本节点以后开始的,而是又从0开始的,解决方法是,设置一个参数start,下次进行递归的时候从start开始,如果不允许结果重复,那么就从start + 1开始递归。很尴尬下道题就是这么出的,所以秒解。还需要注意的一点是当开始递归的时候,传递的参数不是start而是变成了i;
二刷
时间复杂度还是不会算,网上看到大神的答案也没看懂。
接下来就是复杂度分析。这道题的复杂度分析起来比较复杂….一刷的时候就没有好好思考。
首先我们假设这个数组candidate长为n, 在其中有m个元素小于target,假设T(n) = find(target),那么我们可以根据程序得到以下分析结果:
DFS Level 0: 我们首次调用辅助函数, 这一层的count = 1
DFS Level 1: 这时候我们进入了辅助函数的for循环,在for循环里我们有一个pruning,当candidate[i] > target的时候,返回,所以这一层我们只对小于target的元素进行下一层DFS,如我们假设的,结果为m
DFS Level 2: 根据代码,我们上一层有一个target -= num,所以这一层的target其实都不一样。
假设我们对candidate中最小的元素min进行分析,这时候新的target1 = target - min,此时我们要继续计算在candidate数组中有多少元素小于新的target1,假设这个数目为m1,则在这一层我们要对小于target1的m1个数组进行下一层的DFS
假如我们考虑其他非min的元素来计算总的复杂度时, 这时候两层总共要进行1 + m(m1 + m2 + m3 + … + mn)次调用。
由于每一层的target和m都在改变,以我的水平比较难计算出一个漂亮的公式,那么我就想办法简化一下:
这里递归的最大深度是d= target / min, 就是最深我们可以到 target / min这么多层DFS
Branching factor b = m, m是candidate数组里小于等于target的distinct元素的个数。 其实这里多算了,每一层的m都在减少,而且是不规则减少
我们利用DFS公式可以算出这里的Time Complexty = O(md), Space Complexity = O(m)
这只是一个worst case scenario, 更精确的计算还需要再花时间。
二刷CODE
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
Arrays.sort(candidates);
List<Integer> temp = new ArrayList<Integer>();
if(candidates == null || candidates.length == 0)
return result;
sumHelper(result,temp,candidates,target,0);
return result;
}
public void sumHelper(List<List<Integer>> res,List<Integer> temp,int[] nums,int target,int index){
if(target == 0){
res.add(new ArrayList(temp));
return;
}
if(target < 0)
//这里之所以是return不是continue是因为数组是经过排序的
//如果当前的元素大于目标数,就没有必要再继续了,后面的肯定也大于
return;
for(int i = index;i < nums.length;i++){
if(nums[i] > target)
return;
//注意这里是i大于index
if(i > index && nums[i] == nums[i - 1])
continue;
temp.add(nums[i]);
target -= nums[i];
sumHelper(res,temp,nums,target,i);
temp.remove(temp.size() - 1);
target += nums[i];
}
}
}