解决一个回溯问题,实际上就是一个决策树的深度优先遍历过程。
你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
回溯算法的模板:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。
典型题目:
全排列
输入一组不重复的数字,返回它们的全排列。
List<List<Integer>> res = new LinkedList<>();
/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
// 这里的 选择列表 即包含在nums中
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中的元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择,返回上一层决策树
track.removeLast();
}
}
组合问题2
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
class Solution {
List<List<Integer>> res;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new ArrayList<>();
LinkedList<Integer> list = new LinkedList<>();
Arrays.sort(candidates); //排序以便于后面剪枝
backTrace(candidates, 0, target, list);
return res;
}
// start 用于防止产生重复解,如[2,2,3]、[2,3,2]、[3,2,2]就属于重复解
// start表示可使用的数字的范围为 nums[start, ... , nums.length-1],即只能使用索引大于等于start的数字
public void backTrace(int[] nums, int start, int target, LinkedList<Integer> list){
if(target == 0){
// target==0 表示已经找到一个解,new一个新的List放进res。
res.add(new LinkedList<Integer>(list));
return;
}
// 从start开始遍历,通过 nums[i]<=target 进行剪枝
for(int i=start; i<nums.length && nums[i]<=target; i++){
list.add(nums[i]); // 将数字nums[i]暂时放入list
backTrace(nums, i, target-nums[i], list);
list.removeLast(); //关键点:回溯,将前面添加的数字删除
}
return;
}
}