题目描述
给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为: [ [7], [2,2,3] ]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为: [ [2,2,2,2], [2,3,3],[3,5] ]
这道题目我们使用回溯算法来解决,那么回溯算法究竟是什么呢?它其实是一个类似枚举的过程,主要是在搜索尝试的过程中寻找问题的解,当他发现已经不求解条件时,就回溯“回溯”返回。解决一个回溯问题,实际上就是一个决策树的遍历过程,你只需要考虑3个问题:
- 路径:也就是已经做出的选择
- 选择列表:也就是你当前可以做的选择
- 结束条件:也就是到达决策树底层,无法再做选择的条件
回溯算法的框架
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
代码示例
/**
* 回溯算法
*/
// 创建双向链表
public List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
LinkedList<Integer> track = new LinkedList<>();
Arrays.sort(candidates);
// candidates是选择列表; target用来判断是否结束及用于剪枝; track是路径,即已经做出的选择
backtrack(candidates, 0, target, track);
return res;
}
public void backtrack(int[] candidates, int start, int target, LinkedList<Integer> track) {
// 判断结束条件
if(target < 0) {
return ;
}
if(target == 0) {
res.add(new LinkedList<>(track));
return ;
}
for(int i=start; i<candidates.length; i++) {
if(target < candidates[i]) {
break;
}
// 做选择(剪枝)
track.add(candidates[i]);
// 回溯(从start开始,去重)
backtrack(candidates, i, target-candidates[i], track);
// 撤销选择
track.removeLast();
}
}