Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

组合总和

来源Leetcode第39题组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

回溯

emm,这题想了想,要重复这一点属实把我难到了,一时没想出解决办法,就看了解答。

解答采用了回溯 + 剪枝的思路,其实回溯 + 剪枝在课设生成数独的时候做过了,然而已经完全忘了。
先上图

说明:

  • 1.一个蓝色正方形表示的是 “尝试将这个数到数组 candidates 中找组合”,那么怎么找呢?挨个减掉那些数就可以了。

  • 2.在减的过程中,会得到 00 和负数,也就是被我标红色和粉色的结点:

    • 得到 00 是我们喜欢的,从 00 这一点向根结点走的路径(很可能只走过一条边,也算一个路径),就是一个组合,在这一点要做一次结算(把根结点到 00 所经过的路径,加入结果集)。

    • 得到负数就说明这条路走不通,没有必要再走下去了。

总结一下:在减的过程中,得到 00 或者负数,就没有必要再走下去,所以这两种情况就分别表示成为叶子结点。此时递归结束,然后要发生回溯。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Solution {

private List<List<Integer>> res = new ArrayList<>();
private int[] candidates;
private int len;

private void findCombinationSum(int residue, int start, Stack<Integer> pre) {
if(residue < 0)
return;
if (residue == 0) {
// Java 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来
res.add(new ArrayList<>(pre));
return;
}
// 优化添加的代码2:在循环的时候做判断,尽量避免系统栈的深度
// residue - candidates[i] 表示下一轮的剩余,如果下一轮的剩余都小于 0 ,就没有必要进行后面的循环了
// 这一点基于原始数组是排序数组的前提,因为如果计算后面的剩余,只会越来越小
for (int i = start; i < len && residue - candidates[i] >= 0; i++) {
pre.add(candidates[i]);
// [关键]因为元素可以重复使用,这里递归传递下去的是 i 而不是 i + 1
findCombinationSum(residue - candidates[i], i, pre);
pre.pop();
}
}

public List<List<Integer>> combinationSum(int[] candidates, int target) {
int len = candidates.length;
if (len == 0) {
return res;
}
Arrays.sort(candidates);
this.len = len;
this.candidates = candidates;
findCombinationSum(target, 0, new Stack<>());
return res;
}
}

这是没有用栈的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
backtrack(candidates, target, res, 0, new ArrayList<Integer>());
return res;
}

private void backtrack(int[] candidates, int target, List<List<Integer>> res, int i, ArrayList<Integer> tmp_list) {
if (target < 0) return;
if (target == 0) {
res.add(new ArrayList<>(tmp_list));
return;
}
for (int start = i; start < candidates.length; start++) {
if (target < candidates[start]) break;
//从第0个元素开始进行递归
tmp_list.add(candidates[start]);
//采用减法进行回溯
backtrack(candidates, target - candidates[start], res, start, tmp_list);
//删去最后一个新添加的元素,然后进行下一层的回溯
tmp_list.remove(tmp_list.size() - 1);
}
}
}