回溯法总结----由浅入深

参考:https://blog.csdn.net/versencoder/article/details/52071930

https://blog.csdn.net/versencoder/article/details/52072350

起手式:

回溯法,就是试探法,按照优选条件去向前搜索,以达到目标。

但是在搜索到某一步时,发现原先这样并不能满足条件,就回退一步重新选择,这种走不通就退回再走的技术成为回溯法。

在做回溯法的题目的时候,有添加状态或元素就一定有与之对应的回退状态和元素。若是寻找成功,回退以查看有没有其他满足条件的解;如果寻找不成功,回退以查看其它情况。

例题: 从1,... ,n中取出k个数,要求不重复。

n=4, k=2,   1,2 1,3 1,4 2,3 2,4 3,4

题目框架:

public class Solution {
   public List<List<Integer>> combine(int n, int k) {

    }
}

我们很容易想到,先建一个全部变量

public List<List<Integer>> result = new ArrayList<>();

然后我们可以新建一个回溯函数的形式,以迭代调用:

backtracking(int n, int k, int start,List<Integer> list)

其中 n代表总数n, k代表还要取出多少个数,start代表起始取数位置, list用来保存已知的解。

这些肯定都是要用到的吧,然后来看看具体的回溯函数:

    public  List<List<Integer>> result = new ArrayList<>();
    public  List<List<Integer>> combine(int n, int k) {
        List<Integer> list = new ArrayList<>();
        backtracking(n, k, 1, list);
        return result;
    }

    public  void backtracking(int n, int k, int start,List<Integer> list){
        if(k<0){
            return;
        }else if(k==0){
            result.add(new ArrayList<>(list));
        }else{
            for (int i = start; i <= n; i++) {
                list.add(i);                      // 添加元素
                backtracking(n,k-1,i+1,list);
                list.remove(list.size()-1);       // 回退
            }
        }
    }

具体思路:

1.对于n=4,k=2,1,2,3,4中选2个数字,我们可以做如下尝试,加入先选择1,那我们只需要再选择一个数字,注意这时候k=1了(此时只需要选择1个数字啦)。当然,我们也可以先选择2,3 或者4,通俗化一点,我们可以选择(1-n)的所有数字,这个是可以用一个循环来描述?每次选择一个加入我们的链表list中,下一次只要再选择k-1个数字。那什么时候结束呢?当然是k<0的时候啦,这时候都选完了。

2.加入了一个start变量,它是i的起点。为什么要加入它呢?比如我们第一次加入了1,下一次搜索的时候还能再搜索1了么?肯定不可以啊!我们必须从他的下一个数字开始,也就是2 、3或者4啦。所以start就是一个开始标记这个很重要。

3.加上:list.remove(list.size()-1);他的作用就是每次清除一个空位 让后续元素加入。寻找成功,最后一个元素要退位,寻找不到,方法不可行,那么我们回退,也要移除最后一个元素。


起手式变形:
给定一个数组{1,2,3},得出所有的组合方式[],1,2,3,12,13,23,123。

同样可以用回溯法解决,求出组合在暴力求解的时候很有用,用于枚举出所有可能结果,再一一判断。

    public ArrayList<ArrayList<Integer>> combine(int[] nums){
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        ArrayList<Integer> list = new ArrayList<>();
        backtracing(result,list,0,nums);
        return result;
    }

    public void backtracing(ArrayList<ArrayList<Integer>> result,ArrayList<Integer> list,int start, int[] nums){
        result.add(new ArrayList<>(list));
        for (int i = start; i < nums.length; i++) {
            list.add(nums[i]);
            backtracing(result,list,i+1,nums);
            list.remove(list.size()-1);
        }
    }

进阶式:

分割字符串: 

给一个字符串,你可以选择在一个字符或两个相邻字符之后拆分字符串,使字符串由仅一个字符或两个字符组成,输出所有可能的结果

样例:

给一个字符串"123"
返回[["1","2","3"],["12","3"],["1","23"]]

分析:我们可以设置一个起始位置start,每次我们都从start位置开始向后划分一个或者两个字符出去,这样就有很多种划分路径,当一种路径走到末端的时候,之前划分出去的需要回退以再次以不同形式划分。

    public static  List<List<String>> result = new ArrayList<>();    // 首先新建一个 result
    public static List<List<String>> splitString(String s) {
        List<String> list = new ArrayList<>();
        backtraceing(s, 0, list);                                    // 回溯函数
        return result;
    }
    public static void backtraceing(String s, int start, List<String> list){ // start标注每次的起始位置
        if(start>s.length()){
            return;
        }else if(start==s.length()){
            result.add(new ArrayList<>(list));
        }else{
            for (int i = start; i<start+2 && i < s.length(); i++) {   // i<start+2: 每次划分一个或者两个字符
                String subString = s.substring(start,i+1);
                list.add(subString);
                backtraceing(s,i+1,list);
                list.remove(list.size()-1);
            }
        }
    }

高阶式:

例:矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
 
    }
}
思路:1.  在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的

第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。

         2.  找下一个节点时,可能是刚刚搜索过的位置,所以需要新建一个visited数组记录下一个节点是否被访问过。

参考代码:

    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix==null || matrix.length==0 || str==null || str.length==0 || rows<=0 || cols<=0 ||
                rows*cols < str.length){
            return false;
        }
        boolean[] visited = new boolean[rows*cols];
        char[] path = null;
        int start = 0;                       //表示str的起始位置
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if(isbacktracing(matrix, i, j, rows, cols, str, visited, start)){   // 从第i,j个位置是否能走通
                    return true;
                }
            }
        }
        return false;

    }
    public boolean isbacktracing(char[] matrix,int i, int j,int rows,int cols,char[] str,boolean[] visited,int start){
        if(start >= str.length){
            return true;   
        }else {
            int index = i*cols + j;     //  记录当前访问的节点
            if(i<0 || i>=rows || j<0 || j>=cols || matrix[index] != str[start] || visited[index]==true){ //判断是否合适或者是否已被访问
                return false;
            }
            visited[index] = true;      //  标记已读
            boolean judge = isbacktracing(matrix,i+1,j,rows,cols,str,visited,start+1)||
                    isbacktracing(matrix,i,j+1,rows,cols,str,visited,start+1)||
                    isbacktracing(matrix,i-1,j,rows,cols,str,visited,start+1)||
                    isbacktracing(matrix,i,j-1,rows,cols,str,visited,start+1);
            if(judge){
                return true;            
            }
            visited[index] = false;     // 回退
            return false;               // 搜索不到,返回false
        }
    }



  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值