Leetcode组合总和系列——回溯(剪枝优化)+动态规划
组合总和 I
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
此题要求解出所有可能的解,则需要用回溯法去回溯尝试求解,我们可以画一棵解空间树:
图中绿色节点表示找到了一种可行解,而红色的节点表示到这个节点的时候组合总和的值已经大于target了,无需继续向下尝试,直接返回即可。
因为题目要求解集无重复,即2,2,3
和3,2,2
应该算作同一种解,所以我们在回溯的时候应该先对candidates
数组排序,然后每次只向下回溯大于等于自己的节点。
观察解空间树我们发现:当某一层中第一次出现红色节点或绿色节点后,后面的节点将全变为红色,因为数组是经过排序的,任意节点后面的节点都是大于此节点的(candidates
数组无重复元素),所以当出现一个红/绿色节点后,后面的节点不必再继续检查,直接剪枝
即可。
剪枝后的解空间树如下:
这样看整棵解空间树就小多了,下面直接上代码:
Java版本的回溯解法代码
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
dfs(candidates,target,0,new ArrayList());
return result;
}
public void dfs (int[] candidates, int target, int currSum, List<Integer> res) {
if (currSum == target) {
result.add (new ArrayList(res));
return;
}
for (int i = 0; i < candidates.length; i++) {
if (currSum + candidates[i] > target) {
return;
}
int size = res.size();
if (size==0 || candidates[i] >= res.get(size-1)) {
res.add(candidates[i]);
dfs(candidates, target, currSum+candidates[i],res);
res.remove(size);
}
}
}
}
Go版本的回溯解法代码
func combinationSum(candidates []int, target int) (result [][]int) {
sort.Ints(candidates)
var dfs func(res []int, currSum int)
dfs = func(res []int, currSum int) {
if currSum == target {
result = append(result, append([]int(nil), res...))