这题有几个主要的点,
首先,题目要求输出所有的组合数,而不是数据所有组合总的个数,如果是求个数用动态规划更好解决。但是要输出所有的组合数的话使用回溯法更容易处理。
其次,题目要求不能包含有重复的组合,这是解决这个题目的一个难点。
最后,保存解决方案的时候要注意Java里List的浅拷贝和深拷贝,不然浅拷贝最后得到空值。
解法:
其实回溯的方法还是比较简单的,为了后面处理的适合容易去重和剪枝优化,首先需要对数组进行排序。
排序后,从第一个元素开始搜索,可以重复添加元素,一直累加,并保存每个值到List。
如果这个和等于target,就保存这个List,然后继续回溯。
如果和大于了target则直接返回。
这里去重的关键一步是回溯的时候,在选择下一个值时,循环不能从0开始,否则会出现重复的情况。
这时应该跳过之前已经循环过的值,而寻找后面比当前值更大的值。
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum (int[] candidates, int target) {
Arrays.sort(candidates);
int sum = 0;
if(candidates == null || candidates.length == 0)
return res;
List<Integer> list = new ArrayList<>();
trace(candidates,target,sum,list,0);
return res;
}
/**
* 回溯,help(candidates, target-candidates[i], res, path, i);使用减法的比加法的用时更少
*/
public void trace(int[] candidates, int target , int sum , List<Integer> list,int start){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(list));
return;
}
// 去重的关键在于j的值不能从0开始,而应该是start开始。
for(int j = start; j < candidates.length;j++){
if(candidates[j] > target)
break;
list.add(candidates[j]);
trace(candidates,target,sum + candidates[j],list,j);
list.remove(list.size() - 1);
}
}
比较一下
int[] candidates = {2,3,6,7};
target = 7时去重和没去重的回溯的区别,没去重时:
[2, 2, 2, 2]
[2, 2, 2, 3]
[2, 2, 2, 6]
[2, 2, 2, 7]
[2, 2, 3]
[2, 2, 6]
[2, 2, 7]
[2, 3, 2] // 最后2比3小
[2, 3, 3]
[2, 3, 6]
[2, 3, 7]
[2, 6]
[2, 7]
[3, 2, 2] // 中间2比3小
[3, 2, 3] // 中间2比3小
[3, 2, 6] // 中间2比3小
[3, 2, 7] // 中间2比3小
[3, 3, 2] // 最后2比3小
[3, 3, 3]
[3, 3, 6]
[3, 3, 7]
[3, 6]
[3, 7]
[6, 2] // 最后2比6小
[6, 3] // 最后3比6小
[6, 6]
[6, 7]
[7]
去重后:
[2, 2, 2, 2]
[2, 2, 2, 3]
[2, 2, 2, 6]
[2, 2, 2, 7]
[2, 2, 3]
[2, 2, 6]
[2, 2, 7]
[2, 3, 3]
[2, 3, 6]
[2, 3, 7]
[2, 6]
[2, 7]
[3, 3, 3]
[3, 3, 6]
[3, 3, 7]
[3, 6]
[3, 7]
[6, 6]
[6, 7]
[7]