回溯问题
一、基本概念
回溯算法实际上是一个枚举的过程,在搜索尝试过程中寻找问题的解,发现不满足求解回溯返回。
基本思想:
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。
1、若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
2、而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
步骤
(1)针对所给问题,确定问题的解空间:
首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
(2)确定结点的扩展搜索规则。
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
public void backtrack(index){
if(回溯到边界){
输出结果;
return;
}
for(i=下界;i<=上界;i++){
if(i满足限界条件和约束条件){
保存i到临时变量中; //代表尝试一次
backtrack(index+1); //深度遍历,向下遍历一层
清理变量等; //回溯法的关键,相当于本节点清除,然后重新回溯
}
}
}
二、常见问题
- 求全排列 (46,47)
- 求组合 (77)
- 求二叉树和为定值的某一路径
- 求数组中和为定值的子数组(39,40,216)
- 子数组问题(78,90)
- 八皇后问题
- 数独问题
三、LeetCode题解
以下是LeetCode中的部分问题。
LeetCode 39. Combination Sum
原题目连接
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
例子:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
典型的回溯题,安装上面的通用算法,
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//此处res可以用参数传入回溯函数,也可以用类的属性方法实现
List<List<Integer>> result = new ArrayList<List<Integer>>();
backtrack(0,0,target,candidates,result, new ArrayList<Integer>());
return result;
}
//回溯函数的定义是回溯问题的关键,
//保存回溯的当前位置,当前计算结果,目标结果很重要
public void backtrack(int start,int sum,int target,int[] candidates,List<List<Integer>> res,List<Integer> ans){
//回溯到边界,满足条件,直接输出结果到res
if(target == sum){
res.add(new ArrayList<Integer>(ans));
return;
}
for(int i=start;i<candidates.length;i++){
ans.add(candidates[i]); //进行一次尝试
backtrack(i,sum+candidates[i],target,candidates,res,ans); //深度访问下一层
ans.remove(ans.size()-1); //清理本次的尝试,然后重新回溯
}
}
LeetCode 40. Combination Sum II
原题目连接
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
例子:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
//为了保证每个元素只使用一次,先对数组排序
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<List<Integer>>();
backtrack(0,target,candidates,result, new ArrayList<Integer>());
return result;
}
public void backtrack(int start,int target,int[] candidates,List<List<Integer>> res,List<Integer> ans){
//回溯到边届,直接输出结果
if(0 == target){
res.add(new ArrayList<Integer>(ans));
return;
}
//target>=candidates[i]相当于剪支法,不满足的条件可以不用继续回溯
for(int i=start;i<candidates.length && target>=candidates[i];i++){
//对于