题目
解析
思路:这是一道在选择数字的题,条件是数字和为目标值。数字可以被重复选择,集合不能包含重复组合。假设我们从其中选取了一个数a,那么下一个数字依然可以从所有的数字中取选择,新的目标值改变为target-a。接下来的每一步都是一样,都可以用相同的公式去迭代,如果在方法进入后发现传入的target值小于0了,那么就“剪枝”。如果target值正好等于0,那么这就是我们要的组合,在递归的过程中需要传递一个ArrayList来保存被选择的元素。因为每一步都有相同的地方而且传入的数组和目标值都是一个很不确定地值,那么就递归吧!
实例化接收ArrayList并处理特殊情况:
一般特殊的输入在开头判断并返回就可以。
ArrayList<List<Integer>> arrayList=new ArrayList<>();
ArrayList<Integer> array=new ArrayList<>();
if(candidates==null||candidates.length==0){
return arrayList;
}
排序: Arrays.sort(candidates);这步很关键,也是我最初没有考虑到的,它可以避免后面重复组合的产生,因为数字可以被重复选取,假设有223这样的一个数组,要求7这个结果,得到的组合是2+2+3。通过第一个2以及3就可以得到结果,如果再拿后面的2再考虑一次,就有重复了。再假设这个顺序是乱的,还会有2+3+2这样的结果产生。我们的递归思路是有条理地从左到右选择。所以排序可以把重复的数放在一起,然后“滤去”重复的数字。关于新递归的选择,假设数组为123456,在选择了1之后,下一个是123456都有可能,假设选择了第二次选择了3,下一个是3456都有可能。总之,排序可以避免重复,增加条理性。
递归的方法:
进入的时候先判断要不要添加到结果集。是否要剪枝。如果大于0,就从start开始遍历后面的下标,尝试选取这些值并开始新的find,新的find的目标值需要减去选取的那个值。
public void find(int[] candidates,ArrayList<List<Integer>> arrayList,ArrayList<Integer> array, int target , int start){
if(target==0){
arrayList.add(array);
}else if(target<0){
return;
}else{
for(int i=start;i<candidates.length;i++){
if(candidates[i]<=target){
if(i>start&&candidates[i]==candidates[i-1]) continue;
ArrayList<Integer> arrayClone=new ArrayList<>(array);
arrayClone.add(candidates[i]);
find(candidates,arrayList, arrayClone, target-candidates[i], i);
}
}
}
}
深拷贝:ArrayList arrayClone=new ArrayList<>(array);实现了深拷贝,使得每一条支路都彼此独立,深拷贝的方式是把旧List传入新List的构造方法,获取一个新的List的引用,它们的元素相同,但内存彼此独立,引用也不同。
**if(i>start&&candidates[i]==candidates[i-1]) continue;**实现了去重,理由在上面已经提到。
完整代码(Java实现):
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
ArrayList<List<Integer>> arrayList=new ArrayList<>();
ArrayList<Integer> array=new ArrayList<>();
if(candidates==null||candidates.length==0){
return arrayList;
}
Arrays.sort(candidates);
find(candidates, arrayList, array, target, 0);
return arrayList;
}
public void find(int[] candidates,ArrayList<List<Integer>> arrayList,ArrayList<Integer> array, int target , int start){
if(target==0){
arrayList.add(array);
}else if(target<0){
return;
}else{
for(int i=start;i<candidates.length;i++){
if(candidates[i]<=target){
if(i>start&&candidates[i]==candidates[i-1]) continue;
ArrayList<Integer> arrayClone=new ArrayList<>(array);
arrayClone.add(candidates[i]);
find(candidates,arrayList, arrayClone, target-candidates[i], i);
}
}
}
}
}
组合总和II
题目:题目稍作修改,变为数字不能重复选择。
思路:只要在遍历的时候把下标加1,从选取的那个值后面开始递归就可以了。别的地方和上面一样。
代码:
public class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
ArrayList<List<Integer>> arrayList=new ArrayList<>();
ArrayList<Integer> array=new ArrayList<>();
if(candidates==null||candidates.length==0){
return arrayList;
}
Arrays.sort(candidates);
find(candidates, arrayList, array, target, 0);
return arrayList;
}
public void find(int[] candidates,ArrayList<List<Integer>> arrayList,ArrayList<Integer> array, int target , int start){
if(target==0){
arrayList.add(array);
}else if(target<0){
return;
}else{
for(int i=start;i<candidates.length;i++){
if(candidates[i]<=target){
if(i>start&&candidates[i]==candidates[i-1]) continue;
ArrayList<Integer> arrayClone=new ArrayList<>(array);
arrayClone.add(candidates[i]);
find(candidates,arrayList, arrayClone, target-candidates[i], i+1);
}
}
}
}
}