class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
}
}
解题思路:
这道题的算法用的是递归算法,主要的递归流程是这样的。
经过归纳总结,递归一般来说分成这3部分的核心部分。
一个问题可以先解决其中的一小部分问题然后根据结果处理后得到新的结果的这样一个思想。所以第一步是设计递归的流程思路。
在这道题中,通过数组candidates中的值得到总和target。如果这个组合中存在数组中的一个元素candidates[0],那么我们就要求他的子问题,能否使用candidates中的值得到总和target-candidates[0]
既然要做到无限套娃,那么就必须环环相扣,也就是我们一个函数中的参数可以得到子问题的答案,子问题的答案可以解决父问题。
在这道题中,实际上数组是不会变的,我们可以选择把数组变为全局变量,然后重新写一个方法。
我们也可以每次都去传这个数组,这样就不需要重新写一个方法去调用了。
大多数递归问题在过程中都只是对下一步返回的数据进行处理,那到底是哪一步生成的初始数据呢?
一般来说这种递归题都会有一个边界点,就是当传入的参数符合某一个规则时,返回值是固定的。
比如非常经典的兔子繁殖问题或者是细胞分裂问题中。我们有一个方法,输入天数和起始的兔子只数就能得到该天数时有多少只兔子。如果输入5天,4只,你一定需要计算很久,更不用说输入187天,231只这种复杂的数字了。但是,如果输入的是第一天呢?无论输入多少只,当天的兔子只数肯定就是起始的只数,根本不需要计算,这就是边界点。不需要继续递归可以直接得到答案。
那么在这道题中,边界点就在于target<0和target=0,或者说target=candidates[i],这可以根据自己的逻辑来设计,当总和小于或者等于0时,递归肯定是要停止了,因为继续下去没有数字能够组成负数。
代码实现:
在测试时发现了问题,需要重新设计递归规则。现在的问题在于,如何避免重复。
public List<List<Integer>> combinationSum(int[] candidates, int target) {
int len=candidates.length;
//构建结果集
List<List<Integer>> res=new ArrayList<>();
//循环每一个元素
for(int i=0;i<len;i++){
//得到差值
int newTarget=target-candidates[i];
//边界判断,如果得到的是0,也就是当前值刚好单独组成一个组合
if (newTarget==0){
ArrayList<Integer> list=new ArrayList<>();
list.add(candidates[i]);
res.add(list);
}
//如果还能向下递归,如果差值已经小于0了,就不需要递归了
else if (newTarget>0){
//得到递归的结果
List<List<Integer>> lists = combinationSum(candidates, newTarget);
//在结果集前面加上当前减去的值
for (List<Integer> list : lists) {
list.add(0,candidates[i] );
}
//将得到的结果加入到结果集中
res.addAll(lists);
}
}
return res;
}
解决方案:添加回溯。
其实我也不知道这是不是应该叫做回溯算法,因为在递归回溯算法中,有一种方案是把方法中的参数保存在栈中,不与其他方法共享,这样当方法返回时可以进行独立的处理,不影响其他的递归中的参数。
在这一题中,我们可以这样分析,我们求得包含candidates[i]的组合,然后我们就把candidates[i]从candidates中删除掉,这样后续的循环的递归中就不考虑存在candidates[i]的情况,此时就不会发生重复的情况。
public List<List<Integer>> combinationSum(int[] candidates, int target) {
int len=candidates.length;
int[] thisCandidates=new int[len];
//复制一份数组
System.arraycopy(candidates, 0,thisCandidates ,0 ,len );
//构建结果集
List<List<Integer>> res=new ArrayList<>();
//循环每一个元素
for(int i=0;len!=0;){
//得到差值
int newTarget=target-thisCandidates[i];
//边界判断,如果得到的是0,也就是当前值刚好单独组成一个组合
if (newTarget==0){
ArrayList<Integer> list=new ArrayList<>();
list.add(thisCandidates[i]);
res.add(list);
}
//如果还能向下递归,如果差值已经小于0了,就不需要递归了
else if (newTarget>0){
//得到递归的结果
List<List<Integer>> lists = combinationSum(thisCandidates, newTarget);
//在结果集前面加上当前减去的值
for (List<Integer> list : lists) {
list.add(0,thisCandidates[i] );
}
//将得到的结果加入到结果集中
res.addAll(lists);
}
//删除掉这一个元素
int[] newcandidates=new int[len-1];
System.arraycopy(thisCandidates,1 , newcandidates,0 ,len-1);
len=len-1;
thisCandidates=newcandidates;
}
return res;
}