例题:
题目要求找出数组中可以使数字和等于目标数 target 的所有的不同组合,并以集合的形式返回,同一个数字可以被无限制地选取。
注意:
分析:
假如现在有一个数组candidates = [2, 3, 6, 7],需要组合和为7(target = 7),我们可以先固定2,此时 target = 7 - 2 = 5, 还不够,要继续组合,继续从数组的开始(第一个元素2)进行组合,因为数组中的元素可以被重复选取。 再固定2,2, target = 3,固定2,2,2,target = 1,再固定2,2,2,2,此时 target = -1 < 0,不满足条件,要回溯到2,2,2。 接着和3,6,7组合(保不齐数组中有个元素1)。发现和其他元素组合都无解,然后回溯到2,2,紧接着和3组合,发现此时target = 0,找到一组解:2,2,3。当target < 0 时,表示和 溢出了,需要回溯。
图中蓝色的矩形表示有效解。矩形上面的数字代表还差几就能达到target。
我们可以发现,这棵树里存在很多不必要的递归,比如说取值为负数的都是不必要的。比如当栈中已经固定了2,2,2这三个数,此时target = 1,剩余的数 1 比2,3,6,7都要小,没有必要和它们组合了。可以减枝。当剩余的数 < 待组合的数,就没有必要继续递归了。
像这样:
怎么写代码:
当我们做求组合这类的题目时,参数一般都要带上start(遍历数组中的数字时,它的起点索引),为了避免组合中的重复。再带上一个栈,用来保存组合的元素。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class CombinationLeetcode39 {
public static void main(String[] args) {
List<List<Integer>> lists = combinationSum(new int[]{2, 3, 6, 7}, 7);
for (List<Integer> list : lists) {
System.out.println(list);
}
}
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
dfs(0, target, candidates, new LinkedList<>(), result);
return result;
}
public static void dfs(int start, int target, int[] candidates, LinkedList<Integer> stack, List<List<Integer>> result){
if(target == 0){
result.add(new ArrayList<>(stack));
return;
}
for (int i = start; i < candidates.length; i++) {
int candidate = candidates[i];
if(target < candidate){
continue;
}
stack.push(candidate);
dfs(i, target - candidate, candidates, stack, result);
stack.pop();
}
}
}