1、题目链接
2、题目大意
给定一个无重复数字的数组 candidates,以及一个目标数字 target,从数组中抽取任意个数字,每个数字可抽取多次,使得抽取的数字之和等于目标数字,问这样的抽取方法有多少种,要求抽取的方法不能重复,比如以下两种便是重复的情况:[1,2,3] 和 [3,1,2]。
注:题中出现的所有数字都是正整数。
3、样例输入
candidates = [2,3,6,7], target = 7
candidates = [2,3,5], target = 8
4、样例输出
[ [7], [2,2,3] ]
[ [2,2,2,2], [2,3,3], [3,5] ]
5、思路
看到这个题,首先想到的就是暴力做法,就是将全部的组合都排列出来,找出组合中等于目标数的组合。然后再看题目中给的数据范围:
- 1 <= candidates.length <= 30
- 1 <= candidates[i] <= 200
- candidate 中的每个元素都是独一无二的。
- 1 <= target <= 500
看这个数据范围,数组的长度最多也才30,暴力的做法是可以试一下的。
刚才说到将全部的数字组合都找出来,这个可以使用回溯法,也就是递归来实现,但是题目中明确说到,每个数字是可以重复使用的,那么全部组合数就是无限了,因此要进行剪枝,最简单的剪枝就是将组合里数字和大于目标数的分支剪掉,因此本题使用回溯法+剪枝。
题中还提到一个要求,最后求得的组合不能重复,那么需要在递归函数中加入一个参数,用来记录当前已经使用到了哪一个数字,每次查找一个新的数字组合,都是在以该参数为起始点到最后一个数字的范围内查找,那么就可以避免组合重复了。
6、代码
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
dfs(candidates, target, 0, new ArrayList<>(), ans);
return ans;
}
private void dfs(int[] candidates, int target, int index, List<Integer> list, List<List<Integer>> ans) {
if (target == 0) {
ans.add(list);
return;
}
for (int i = index; i < candidates.length; i++) {
if(target>=candidates[i]) {
list.add(candidates[i]);
dfs(candidates, target-candidates[i], i, new ArrayList<>(list), ans);
list.remove(list.size() - 1);
}
}
}
}
7、注意事项
代码中有个点需要特别注意,大家看下面这句代码:
dfs(candidates, target-candidates[i], i, new ArrayList<>(list), ans);
可以看到传参中,我在传 list 的时候,新建了一个列表,这是因为如果我直接将list传进去,那么在递归栈中的所有 list 都是同一个对象,那么在 ans 列表中存的 list 也都是同一个对象,可以将上述代码改为下列代码做一个测试:
dfs(candidates, target-candidates[i], i, list, ans);
可以发现,无论怎样,最后的 ans 输出的值都是空的。这是因为存在 ans 里的列表都是最初传进来的 list,我们在递归的时候,做的操作是先往 list 里丢一个数,然后递归到下一层,等递归完后,再把丢进去的书移除,那么到最后,其实 list 还是刚开始的样子,是空,而 ans 里存的就是这个为空的 list。