回溯算法也是常见的算法,使用回溯法要么是求所有的可行解要么是求最优解。无论哪一种,都需要找出所有的可行解,进行比较。因此回溯法本质上还是深度优先遍历。它是将问题转换成图的深度遍历。然后沿着一个方向遍历,遍历到某个和节点的时候,判断这个节点是否可行,如果可行,则以这个节点为根进行子节点深度搜索。如果不可行,则往后退一步到父节点,然后继续判断,也就是说回溯一般使用递推的方式实现。
有上面可知,首先要将问题转化为图的形式,然后开始遍历,每一个节点的下一个节点不止一个,我们将所有的下一个节点放在一个列表里,就叫做选择列表;而且还需要一个列表记录现在已经走完的路径
而且我们还需要一个结束的条件,满足这个结束条件后,那么这个方向上的路径就被记录下来,并且结束。
此外还有一个选择机制,根据自己所需,进行选择。
综上所述模板伪代码如下
result={}存放结果
void backtrack(此时已经走完的路径,此次可选择的选择列表){
if 满足结束条件:
result.add(路径);
return;//一定要加,这个表示这个方向的结束;
for(int i=0;i<选择列表长度;i++){//这里要遍历所有的选择,将所有的选择考虑进去
if 是否满足需求:
放入已经走完的路径里;
else
continue;
backtrack(此时已经走完的路径,此时的选择列表);//注意这里的选择列表已经发生了变化,是刚放进路径的节点下一步的所有选择
撤销上一步的路径;
}
}
下面我们结合例题来看:
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
代码如下
class Solution {
List<List<Integer>> lists=new ArrayList<>(); //用来存放所有的结果
List<Integer> list=new ArrayList<>(); //这个就是路径,存放被选择过的节点
public List<List<Integer>> combine(int n, int k) {
backtrack(k,0,n);
return lists;
}
public void backtrack(int k,int x,int n){ //通过x来记录选择列表
if(list.size()==k){
lists.add(new ArrayList<>(list));
return;
}
for(int i=x+1;i<=n;i++){
list.add(i);
backtrack(k,i,n);
list.remove(list.size()-1);
}
}
}
给定一个无重复元素的数组 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] ]
class Solution {
List<List<Integer>> lists=new ArrayList<>();
List<Integer> list= new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
backtrack(candidates,target,0);
return lists;
}
public void backtrack(int[] candidates,int target,int start){
if(target==0){
lists.add(new ArrayList<>(list));
return;
}
if(target<candidates[0]) return;
for(int i=start;i<candidates.length;i++){
if(target-candidates[i]>=0){
list.add(candidates[i]);
backtrack(candidates,target-candidates[i],i);//注意这里,因为题目是每个元素都可以重复使用,如果不可以 重复,就i+1
list.remove(list.size()-1);
}
}
}
}
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。 示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ] 示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 所求解集为: [ [1,2,2], [5] ]
代码如下
class Solution {
List<List<Integer>> lists=new ArrayList<>();
List<Integer> list= new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtrck(candidates,target,0);
return lists;
}
public void backtrck(int[] candidates,int target,int x){
if(target==0){
lists.add(new ArrayList<>(list));
return;
}
if(target<candidates[0]) return;
for(int i=x;i<candidates.length;i++){
if(target-candidates[i]>=0){
if(i>x && candidates[i]==candidates[i-1]) continue; //这个要防止重复的组合,比如这里有两个1,都可以和7组合成8
list.add(candidates[i]);
backtrck(candidates,target-candidates[i],i+1);//由于元素不可重复使用,所以要下一位
list.remove(list.size()-1);
}
}
}
}
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。 解集不能包含重复的组合。 示例 1:
输入: k = 3, n = 7 输出: [[1,2,4]] 示例 2:
输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
结合上一题和第一题的组合,可得代码如下
class Solution {
List<List<Integer>> lists=new ArrayList<>();
List<Integer> list =new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtrack(n,k,1);
return lists;
}
public void backtrack(int n,int k,int x){
if(list.size()==k&&n==0 ){
lists.add(new ArrayList<>(list));
return;
}
for(int i=x;i<10;i++){
list.add(i);
backtrack(n-i,k,x+1);
list.remove(list.size()-1);
}
}
}