题目表述
解题思路
标准的回溯方法模板题,使用回溯三部曲可以解决问题
回溯三部曲:
- 递归函数参数
- 递归终止条件
- 单层搜索逻辑
- 这里根据题目的数据范围可以确定,给定的数组中不存在0元素,避免的无限相加的情况。
- 本题类似于回溯基础中的组合问题,但是与组合问题不同,这里可以重复使用给定数组中的元素。不需要考虑重复的问题。
- 回溯问题一般都抽象为树形结构,本题抽象成树形结构如下:
注意图中叶子节点的返回条件,因为本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
递归三部曲:
- 递归函数的参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。
递归函数的参数可以根据实际的需要来进行设置,无需在一开始就将全部的参数都定义完毕,这里使用了四个参数:
(1)题目给定的集合candidates。
(2)目标值target
(3)数值的总和sum。
(4)for循环的起始位置,对于组合问题,什么时候需要startIndex呢?如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合。 - 递归终止的条件
从树形结构中的叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。sum等于target的时候,需要收集结果。 - 单层循环逻辑
单层for循环从给定的startIndex开始,搜索整个candidates数组。注意本题和77.组合 (opens new window)、216.组合总和III (opens new window)的一个区别是:本题元素为可重复选取的。
代码(原始)
### 解题思路
此处撰写解题思路
### 代码
```java
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> result= new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
callback(candidates, target, 0, 0);
return result;
}
int sum = 0;
private void callback(int[] candidates, int target, int sum, int index) {
if(sum > target) {
// 超过指定的目标数字
return;
}
if(sum == target) {
result.add(new LinkedList<>(path));
return;
}
// 单层循环逻辑
for (int i = index; i <= candidates.length - 1; i++) {
sum += candidates[i];
path.add(candidates[i]);
// 这里传递索引的时候,不需要加1后再传值,因为数字是可以重复使用的,而不是组合问题。
// 组合问题不能使用重复的元素,这里可以使用重复的元素
callback(candidates, target, sum, i);
// 回溯,移除末尾的元素
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
代码(随想录剪枝优化)
// 剪枝优化
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates); // 先进行排序
backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
return res;
}
public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
// 找到了数字和为 target 的组合
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(res, path, candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}