回溯算法是纯暴力算法,虽然并不高效,但有些问题能求出来就好了。
比如组合,排列,分割,子集,棋盘,求树的路径等问题就适合回溯算法,一个问题,如果不确定有多少层循环,就可以使用回溯算法。
事实上回溯算法都可以抽形成树形结构,
结果中的每一个数据,都对应着一层递归。每层递归会把某一位数据可能出现的情况都遍历一次。
以组合问题为例,假设所给数组中没有重复数据。
- 首先一个for循环遍历数组,循环里面放这递归,递归开始前先将当前数据入临时结果,递归函数下面把该数据从临时结果中去掉;
- 下一次递归的for循环就从当前已经使用过的数据的下一个开始;第i层递归,代表的就是组合里面第i个数据的可能情况。
- 最后弄一个结束条件,当开始下标为最后一个数据的时候,代表已经把数据全部遍历了,就将结果放入结果集。这样就可以把不知道有多少层循环的问题给暴力出来。
以leetcode----77为例:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
import java.util.*;
class Solution {
ArrayList<Integer> list=new ArrayList<>();
List<List<Integer>> lists=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
combineDes(n,k,1);
return lists;
}
private void combineDes(int n,int k,int startIndex){//因为是组合,没有顺序,所以有开始下标
if(list.size()==k){//结束条件,并把符合情况的结果加入结果集
ArrayList<Integer> temp=new ArrayList(list);
lists.add(temp);
return;
}
for(int i=startIndex;i<=n;i++){//横向遍历数组,第i次递归,就代表第i位数可能出现的情况
list.add(i);//进入递归就把结果加进去
combineDes(n,k,i+1);//进入递归,排列下一个数可能的情况。
list.remove(list.size()-1);//递归完成之后就把结果移除
}
}
}
如果是排列问题,那么数据就有顺序,这样每层递归的循环都从下标0开始,并且需要增加一个used数组来查看数据是否被用过。
在循环内部,进入递归之前used[i]置为true,代表被使用,递归结束之后置为false,代表没有被使用。
如果数组中存在重复数据,结果集里面的结果不允许重复,那么在当前递归层里面不能重复选取同一个数据。比如在第一层递归里面,如果第一个数据是1,并且进入递归,第二个数据也是1,那么在该层递归里面,第二个1就不应该进入递归,不然就会出现两个1xxx的结果。(也就是arr[i]==arr[i-1]&&used[i-1]==false),used[i]等于false代表进入了递归又出来了。所以不能再进入相同数据的递归,就跳过,因为整个for循环选取的是第i个数据的可能情况,不能出现重复。不过同一个结果里面可以有重复数据,所以arr[i]==arr[i-1]&&used[i-1]==true的时候就可以。
以leetcode------40为例:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
class Solution {
private List<Integer> list=new ArrayList<>();
private List<List<Integer>> lists=new ArrayList<List<Integer>>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if(candidates.length==0){
return lists;
}
Arrays.sort(candidates);
boolean[] used=new boolean[candidates.length];
getPath(candidates,0,0,target,used);
return lists;
}
private void getPath(int[] candidates,int startIndex,int sum,int target,boolean[] used){
if(sum>target){//结束条件
return;
}
if(sum==target){//结束条件
List<Integer> newList=new ArrayList(list);
lists.add(newList);
return;
}
for(int i=startIndex;i<candidates.length&&(sum+candidates[i]<=target);i++){//
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
//如果前一个值和当前值相同,并且前一个值和当前值不在同一个组合里面,就需要跳过。
//used[i-1]=true就代表前一个数据和当前选择的数据在同一个组合里面,前一个数据是上一层递归加入的。
continue;
}
sum+=candidates[i];
list.add(candidates[i]);
used[i]=true;
getPath(candidates,i+1,sum,target,used);
used[i]=false;
sum-=candidates[i];
list.remove(list.size()-1);
}
}
}