回溯
回溯,计算机算法,回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。(来自百度知道)
是不是感觉很麻烦,直接引入例子吧。
例子引入
全排列(leetcode 46)。
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案示例
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
让我们来解解这道题:
由此可以看出这是一棵树,所以我们用树的知识看待这道题。
如果这个数不存在,我们就会往下递归,然后重新从这个数组遍历,如果达到条件(这里条件我们可以看出当数组全部被我们遍历,结果数组长度等于nums长度)的时候就停止递归,或者如果数字存在于其中时候,就停止遍历,并且退回于原来数字,比如1->2->3结束后就会退回到1然后往下递归三,这种停止遍历回退的我们叫做剪枝,就好比树一样,当你减掉枝条其他枝条和树叶就不会在树上了,也就不会往后递归遍历了。
我们通过上面的想法来写写代码
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();//用来存放结果
List<Integer> list = new ArrayList<>();//用来充当当前路径
permute(nums, lists, list);
return lists;
}
private void permute(int[] nums, List<List<Integer>> lists,List<Integer> list){
if(list.size()==nums.length){//满足条件
lists.add(new ArrayList<>(list));//将当前路径加入到结果
return;//递归退出
}
for (int num : nums) {
if (list.contains(num)) continue;//判断是否有当前数
list.add(num);//加入当前数
permute(nums, lists, list);//进入递归
list.remove((Integer) num);//还原结果
}
}
}
我们可以通过上面的代码得出一个回溯的模型:
主方法() {
初始化;
切入点方法();
}
切入点方法(){
if(满足条件){
加入结果;
return;//递归退出
}
for () {
基本判断 ;
加入结果;
切入点方法();
还原结果;
}
}
来做几道题练练手
组合(leetcode77)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
满足条件:当前路径=k
更具条件和模板可以得出代码:
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> list = new ArrayList<>();
combine(n,k,lists,list,1);
return lists;
}
private void combine(int n,int k,List<List<Integer>> lists ,List<Integer> list,int index){
if(list.size()==k){
lists.add(new ArrayList<>(list));
return;
}
for (int i = index; i <=n; i++) {
list.add(i);
combine(n,k,lists,list,i+1);
list.remove((Integer) i);
}
}
}
都觉得这些题太简单了对吧
来一道难一点的
N 皇后(leetcode 51)
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
解释:4 皇后问题存在两个不同的解法.
满足条件:
1.List达到n个元素
2.正斜角和反斜角,上下都不能同时存在两个Q(正斜角的x-y相等,反斜角的x+y相等)
根据上面两个条件可以得出数组一定是一个无重复数字的数组,所以可以得出代码为
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> lists = new ArrayList<>();
List<List<Integer>> ILists = new ArrayList<>();
solveNQueens(n,new ArrayList<>(),ILists);
toStringList(ILists,lists,n);
return lists;
}
private void solveNQueens(int n, List<Integer> IList, List<List<Integer>> ILists) {
if (IList.size() == n) {
if (isTrue(IList)) {
ILists.add(new ArrayList<>(IList));
}
return;
}
for (int i = 0; i < n; i++) {
if(IList.contains(i))continue; //取出重复数字(上下不能重复)
IList.add(i);
solveNQueens(n,IList,ILists);
IList.remove((Integer) i);
}
}
private boolean isTrue(List<Integer> IList) {
for (int i = 0; i < IList.size(); i++) {
for (int j = 0; j < IList.size(); j++) {
if (j == i) continue;
int x = i + IList.get(i);
int y = i - IList.get(i);
int xj = j + IList.get(j);
int yj = j - IList.get(j);
if(x==xj||yj==y)return false; //(如果相等,说明斜角有相同的皇后)
}
}
return true;
}
private void toStringList(List<List<Integer>> ILists,List<List<String>> lists,int n){
for (List<Integer> IList : ILists) {
ArrayList<String> list = new ArrayList<>();
for (int j = 0; j < n; j++) {
StringBuilder sb = new StringBuilder();
for (int k = 0; k < n; k++) {
if (IList.get(j) == k) {
sb.append("Q");
continue;
}
sb.append(".");
}
list.add(sb.toString());
}
lists.add(list);
}
}
}
通过这种模板写回溯,我感觉我刷疯了,你可以去试试,不会可以私信我,如果你也对回溯刷疯了也可以来私信我。