回溯、DFS---JZ65、JZ66、24、27

JZ65矩阵中的路径

参考这里

知识点:
1、使用空字符(Python: '' , Java/C++: '\0' )做标记是为了防止标记字符与矩阵原有字符重复。当存在重复时,此算法会将矩阵原有字符认作标记字符,从而出现错误
2、dfs
3、回溯算法适合用递归实现代码。当我们到达某一个节点时,尝试有可能的选项并在满足条件的前提下递归地抵达下一个节点。
4、DFS模板:

dfs(){
    
    ##一二三步都是递归的出口
 
    // 第一步,检查下标是否满足条件------因为这是递归函数,每一次跳进来之前[i,j]坐标都会变,所以首先需要判断下标是否满足条件;
 
    // 第二步:检查是否被访问过,或者是否满足当前匹配条件------------坐标变了,对应的元素自然变了,所以要判断新元素是否被访问过,或是否满足当前匹配条件,若被访问过或不满足匹配条件,则跳出本次递归,反之继续递归;
 
    // 第三步:检查是否满足返回结果条件------------------继续递归前需要有终止条件;代码走到这说明该元素没被访问,且符合条件。
 
    // 第四步:都没有返回,说明应该进行下一步递归--------------若不满足终止条件,则继续递归
    // 标记
    dfs(下一次)
    // 回溯
} 
 
 
main() {
    
    
    for (对所(原问题)有可能情况) {
    
    //这里原问题是路径的第一步,这里是列举所有可能的第一步;
        dfs()
    }
}

题解:
1、以矩阵的的左上角作为路径的起点,开始遍历dfs()【第一个dfs()递推找路径的起点】
2、dfs()的参数是什么,返回值是什么
dfs的目的是搜索矩阵位置[i,j]的字符是否存在于字符串位置pos处,所以参数:bool dfs(int i, int j, int pos, char *str);返回bool型,用来确定是否存在;
3、检查下标是否满足条件;
4、检查是否被访问过,或者是否满足当前匹配条件;
5、检查是否满足返回结果条件
6、都没有返回,说明应该进行下一步递归【第二个dfs()递推,确定一个位置的上右下左是否存在下一个字符】
6.1标记
6.2dfs()
6.3回溯
代码

class Solution {
    
    
public:
    char *mat = 0;
    int h = 0, w = 0;
    int str_len = 0;
    int dir[5] = {
    
    -1, 0, 1, 0, -1};
 
    bool dfs(int i, int j, int pos, char *str) {
    
    
        // 第一步,检查下标是否满足条件=======================================================
        // 因为dfs调用前,没有进行边界检查,
        // 所以需要第一步进行边界检查,
        // 因为后面需要访问mat中元素,不能越界访问
        if (i < 0 || i >= h || j < 0 || j >= w) {
    
    
            return false;
        }
 
        char ch = mat[i * w + j];
        // 第二步:检查是否被访问过,或者是否满足当前匹配条件====================================
        // 判断是否访问过,因为一个格子最多只能访问一次。
        // 如果没有访问过,判断是否和字符串str[pos]匹配
        if (ch == '\0' || ch != str[pos]) {
    
    
            return false;
        }
 
        // 第三步:检查是否满足返回结果条件================================================= 
        // 如果匹配,判断是否匹配到最后一个字符
        if (pos + 1  == str_len) {
    
    
            return true;//第二个dfs递归的返回条件
        }
 
        // 第四步:都没有返回(即,上面一二三步的条件都没满足,return语句都没执行,没提前结束),说明应该进行下一步递归==================================================
        //第4.1步:标记------------------------------------------------------
        // 说明当前字符成功匹配,标记一下,下次不能再次进入
        mat[i * w + j] = '\0';
        //第4.2步:dfs(下一次)--------------------------------------------------
        for (int k = 0; k < 4; ++k) {
    
    
            if (dfs(i + dir[k], j + dir[k + 1], pos + 1, str)) {
    
    //第二个dfs递归,递归某元素的上右下左;这里if语句是找到一条路径才判断为真;s
                return true;//第一个dfs递归的返回条件
            }
        }
        //第4.3步:回溯------------------------------------------------------
        // 如果4个方向都无法匹配 str[pos + 1]
        // 则回溯, 将'\0' 还原成 ch         
        mat[i * w + j] = ch;
        // 说明此次匹配是不成功的
        return false;  //这里返回的是第二个dfs递归,接着上右下左的顺序循环,若已是最后一个左边的判断,则接着回溯到再上一个字符;
    }
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
    
    
    mat = matrix;
        h = rows, w = cols;
         str_len = strlen(str);
 
        for (int i = 0; i < rows; ++i) {
    
    
            for (int j = 0; j < cols; ++j) {
    
    //两个for循环:从矩阵左上角的格子开始一个一个的遍历,作为路径的起点
                if (dfs(i, j, 0, str)) {
    
    //第一个dfs递归
                    return true;
                }
            }
        }
        return false;
    }


};

时间复杂度:O(MN*3^k), 每个位置除当前自己的方向,还有3个方向可以展开。k为str的长度;矩阵中共有 MN 个起点
空间复杂度:O(k), 最大递归栈的深度为k

JZ66机器人的运动范围

题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

题解
1、模式识别:本题是一个搜索问题,需要遍历矩阵。搜索问题一般用深度优先搜索(DFS:递归实现)或广度优先搜索(BFS:利用队列实现)-------本题以DFS分析,一般首先DFS
2、DFS以递归实现,所以要确定原问题和子问题:
原问题:从[i,j]出发能走多少步;
子问题:[i,j]下一步是[i-1,j],[i+1,j],[i,j-1],[i,j+1],递归搜索这四个坐标为起点能走多少步,这是四个子问题。
最后结果 = 所有子问题的解 + 1 ;加1的原因是包括当前[i,j]这个格子。
3、对于矩阵搜索一定要用一个二维数组记录当前的格子是否被访问过。
DFS模板:

dfs(){
    
    
1、递归出口(包括上面模板的一二三步)------下面要进入子问题的递归,进入递归之前当然要先判别递归终止条件;
2、进入子问题递归
  2.1标记
  2.2子问题递归========》》原问题的解
  2.3回溯
}
main{
    
    
1、叛别特殊情况;
2、使用二维数组创建标记矩阵vector<vector<bool>> mark(rows,vector<bool>(cols,0));
3、原问题的递归
}

代码

class Solution {
    
    
public:
    int check(int n){
    
    
        int sum =0;
        while(n){
    
    
        sum+=n%10;
        n/=10;
        }
        return sum;
    }
    int dfs(int x,int y, int rows,int cols,int threshold,vector<vector<bool>> &mark){
    
    //不用引用会超时
        int count =0;
        //第一步递归出口(包含一二三)
        if(x>=0 && x<rows && y>=0 && y<cols && mark[x][y]==false && check(x)+check(y)<=threshold){
    
    
            mark[x][y]=true;
            count=1+dfs(x-1,y,rows,cols,threshold,mark)
                +dfs(x+1,y,rows,cols,threshold,mark)
                +dfs(x,y-1,rows,cols,threshold,mark)
                +dfs(x,y+1,rows,cols,threshold,mark);
        }
        return count;
    }
    int movingCount(int threshold, int rows, int cols)
    {
    
    
        if(threshold<0) return 0;
        vector<vector<bool>> mark(rows,vector<bool>(cols,0));
        int count =0;
        count=dfs(0,0,rows,cols,threshold,mark);
        return count;
    }
};

时间复杂度:O(mn), m,n为矩阵大小,每个元素最多访问过一次

空间复杂度:O(mn),其中 m 为方格的行数,n 为方格的列数。搜索的时候需要一个大小为 O(mn) 的标记结构用来标记每个格子是否被走过。

优化
因为所有解是连通的,所以只需要搜索下右两个方向即可。(由于解是连通的,所以即使一个位置向下右搜索不到应有的解,它也会在回溯上一个位置后,通过下右搜索到)

class Solution {
    
    
public:
    int check(int n){
    
    
        int sum =0;
        while(n){
    
    
        sum+=n%10;
        n/=10;
        }
        return sum;
    }
    int dfs(int x,int y, int rows,int cols,int threshold,vector<vector<bool>> &mark){
    
    //不用引用会超时,使用“传引用参数”的形式可以避免拷贝实参,之所以使用引用,是因为拷贝大的“类类型对象”或“容器对象”比较低效,甚至有的类类型根本不支持拷贝操作;
        int count =0;
        //第一步递归出口(包含一二三)
        if(x>=0 && x<rows && y>=0 && y<cols && mark[x][y]==false && check(x)+check(y)<=threshold){
    
    
            mark[x][y]=true;
            count=1+dfs(x+1,y,rows,cols,threshold,mark)
                +dfs(x,y+1,rows,cols,threshold,mark);
        }
        return count;
    }
    int movingCount(int threshold, int rows, int cols)
    {
    
    
        if(threshold<0) return 0;
        vector<vector<bool>> mark(rows,vector<bool>(cols,0));
        int count =0;
        count=dfs(0,0,rows,cols,threshold,mark);
        return count;
    }
};

JZ24 二叉树中和为某一值的路径

JZ27 字符串的排列

知识点:

1、函数使用“传引用参数”:
使用“传引用参数”的形式可以避免拷贝实参,之所以使用引用,是因为拷贝大的“类类型对象”或“容器对象”比较低效,甚至有的类类型根本不支持拷贝操作;

猜你喜欢

转载自blog.csdn.net/qq_42647047/article/details/110389346