39.组合总和
39.组合总和
题目
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
思路
这道题的本质还是枚举,找出所有符合条件的情况,那么可以使用回溯算法。
candidates中的数字可以无限制重复被选取,说明每次可取的元素都是整个数组。
抽象树
从抽象树可以看出以下几个问题:
1.递归的次数是不确定的,那么就需要考虑什么时候停止递归?
2.给定的candidates数组是否有序的?如果有序那么当前元素+之前线路总和>target,之后的元素也不用取值了。
3.解集不能包含重复的组合,如何避免重复?
问题的解
1.给定的candidates数组是无序的,那么递归的终止条件是sum>=target
2.避免重复就得区分排列和组合
在排列中,[2,2,3]和[2,3,2]不是重复的
在组合中,[2,2,3]和[2,3,2]是重复的
解决重复的办法是使用startIndex控制每次循环的取值
startindex的使用
开始看见可以重复取值就想的不用startIndex了,startIndex是解决重复的关键。
对于组合问题
- 多个集合取组合,各个集合之间互不影响,就不用startIndex
- 一个集合取组合,就需要startIndex
抽象树中就使用了startIndex,为什么取3之后下一次就不取2或者5了?
对于组合来说[2,2,3]与[3,2,2]是重复的,3与2组合结果已经在2与3里讨论过了,避免重复就不需要取2了。
题解
在上述的引入下,套上递归三部曲
递归函数的返回值以及参数
candidates:从candidates数组取值
target:组合的总数
startIndex:控制candidates数组的取值区间
sum:路径总和
List<List<Integer>> res= new ArrayList<List<Integer>>(); //结果集
List<Integer> path = new ArrayList();//记录当前的路径
void backtracking(int[] candidates,int target,int startIndex,int sum);
递归的终止条件
if(sum>target) return
if(sum==target){
res.add(new ArrayList(path));
return;
}
单层递归逻辑
for(int i=startIndex;i<candidates.length;i++){
//当前元素加入路径
int cur = candidates[i];
path.add(cur);
backtracking(candidates,target,i,sum+cur);//可以取重复的元素,控制下一层递归开始取值的下标
path.remove(path.size()-1);
}
代码
class Solution {
List<List<Integer>> res;
List<Integer> path;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new ArrayList<>();
path = new ArrayList<>();
backtracking(candidates,target,0,0);
return res;
}
void backtracking(int[] candidates,int target,int startIndex,int sum){
if(sum > target) return;
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex;i<candidates.length;i++){
path.add(candidates[i]);
backtracking(candidates,target,i,sum+candidates[i]);
path.remove(path.size()-1);
}
}
}
剪枝
在本层递归循环的时候,可以先预判sum+candidates[i]本次循环取值>target?这样可以少做加入path与从path中删除的步骤,以此实现优化。
for(int i=startIndex;i<candidates.length;i++){
if(sum+candidates[i] > target) continue;//修改点
path.add(candidates[i]);
backtracking(candidates,target,i,sum+candidates[i]);
path.remove(path.size()-1);
}
学习-利用排序
做40题时,不会去重,看别人的题解可以利用排序法,所以打算重做此题。
我的题解思路其实提出过考虑给定的candidates数组是否有序的?如果有序candidates[i]+sum>target,i之后元素也不用取值了,从而实现剪枝提高效率。因为给定的candidates时无序的,我就没有继续考虑这种情况了。
按这个思路,可以得出排序的主要目的时剪枝
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> path ;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new ArrayList<>();
path=new ArrayList<Integer>();
Arrays.sort(candidates);//对candidates进行排序,排序时剪枝的前提
backtracking(candidates,target,0,0);
return res;
}
void backtracking(int[] candidates,int target,int startIndex,int sum){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex;i<candidates.length;i++){
if(sum+candidates[i]>target) break; //与之前剪枝的不同点
path.add(candidates[i]);
backtracking(candidates,target,i,sum+candidates[i]);
path.remove(path.size()-1);
}
}
}