面试题12:矩阵中的路径——回溯法

面试题 :请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

注意:

1.应该有一个布尔值矩阵来记录矩阵的哪些格子已经被使用过了
2.使用递归的方式求解。在使用递归的时候应该注意,在退出递归的时候需要根据需求对计数或者标志进行回退或者清除等操作。

算法原理

深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝 。

算法剖析

  • 递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
  • 终止条件
    (1) 返回 false : ① 行或列索引越界 或 ② 当前矩阵元素与目标字符不同 或 ③ 当前矩阵元素已访问过 (③ 可合并至 ② ) 。
    (2) 返回 true : 字符串 word 已全部匹配,即 k = len(word) - 1 。
  • 递推工作
    (1) 标记当前矩阵元素: 将 board[i][j] 值暂存于变量 tmp ,并修改为字符 ‘/’ ,代表此元素已访问过,防止之后搜索时重复访问。
    (2) 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需一条可行路径) ,并记录结果至 res 。
    (3) 还原当前矩阵元素: 将 tmp 暂存值还原至 board[i][j] 元素。
    (4) 回溯返回值: 返回 res ,代表是否搜索到目标字符串。

20211202

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word)
    {
        rows = board.size();
        cols = board[0].size();
        vector<vector<bool>> visited(rows, vector<bool>(cols, false)); // 标记数组,标记是否走过
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                
                if (dfs(board, i, j, word, visited, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>& board, int i, int j, string& word, vector<vector<bool>>& visited, int k)
    {
        // 递归终止条件
        if (i < 0 || i >= rows || j < 0 || j >= cols || word[k] != board[i][j] || visited[i][j]) {
            return false;
        }
        if (k == word.size() - 1) {
            return true;
        }

        // 做选择
        visited[i][j] = true;

        bool res = dfs(board, i - 1, j, word, visited, k + 1) ||
                   dfs(board, i + 1, j, word, visited, k + 1) ||
                   dfs(board, i, j + 1, word, visited, k + 1) ||
                   dfs(board, i, j - 1, word, visited, k + 1);

        // 撤销选择
        visited[i][j] = false;

        return res;
    }
private:
    int cols;
    int rows;
};

剑指offer参考

class Solution {
public:
    //回溯法进行矩阵中的路径判断
    //输入:源数组 matrix, 行数 rows, 列数 cols,目标字符串 str
    //输出:有目标路径 true, 无解 false
    bool hasPath(const char* matrix, int rows, int cols,const char* str){
        
        //1.检查输入变量的合法性
        if(matrix == nullptr || rows <= 0 || cols <= 0 || str == nullptr)
            return false;
        
        //2.标记变量数组的声明与初始化
        bool* visited = new bool[rows * cols];    //动态内存分配:new 类型名(初始化参数列表)
        memset(visited,0,rows * cols);    //初始化数组元素的值
        
        //3.遍历所有可选项,从可选项中选出符合条件的位置,进行回溯
        int pathlength = 0;    //递归深度
        for (int i = 0; i < rows; ++i){
            for (int j = 0; j < cols; ++j){
                if (hasPathCore(matrix, rows, cols, str, i, j, pathlength, visited))    //调用一个函数,这个函数从可选项中选出符合条件的位置,进行回溯,并会返回bool值
                    return true;    //有目标路径返回true
            }
        }
        
        //释放动态分配的内存空间
        delete [] visited;
        
        return false;    //无目标路径返回false
    }
        
    
    //递归方法实现回溯
    //输入:源数组 matrix, 行数 rows, 列数 cols,目标字符串 str,遍历深度 pathlength,标记数组 visited
    //输出:有目标路径 true, 无解 false
    bool hasPathCore(const char* matrix, int rows, int cols,const char* str, int i, int j, int pathlength, bool* visited){ 
        //0.递归结束条件,找到目标路径,回溯结束
        if(str[pathlength] == '\0')    //字符串常量的本质是其首地址
            return true;
        
        bool haspath = false;    //默认无目标路径
        
        //1.递归判断条件,继续递归or返回结果。从可选项中选出符合条件(当前格子和路径字符串中下标为pathlength的字符一样时)的位置,进行回溯
        if(i >= 0 && i < rows && j >= 0 && j < cols && matrix[i * cols + j] == str[pathlength] && !visited[i * cols + j]){
            ++pathlength;
            visited[i * cols + j] = 1;
            
            //2.递归公式,从可选项中找出符合条件的选项,进行下一次递归
            haspath = hasPathCore(matrix, rows, cols,str,i - 1, j, pathlength, visited)
                   || hasPathCore(matrix, rows, cols,str,i + 1, j, pathlength, visited)
                   || hasPathCore(matrix, rows, cols,str,i, j - 1, pathlength, visited)
                   || hasPathCore(matrix, rows, cols,str,i, j + 1, pathlength, visited);
            
            //3.可选项中没有符合条件的结果,退回上一个步骤
            if (!haspath){
                --pathlength;
                visited[i * cols + j] = 0;
            }
        }
        return haspath;                                                                                           
    }
};
  

20200521

//牛客网
class Solution {
public:
    bool hasPath(const char* matrix, int rows, int cols, const char* str) {
        if (matrix == nullptr || rows <= 0 || cols <= 0 || str == nullptr)
            return false;
        bool* visited = new bool[rows * cols];
        memset(visited, 0, rows * cols);

        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {	//根节点
                if (dfs(matrix, rows, cols, str, i, j, visited, 0))
                    return true;    //有一条路径即可
            }
        }
        delete[] visited;
        return false;
    }

    bool dfs(const char* matrix, int rows, int cols, const char* str, int i, int j, bool* visited, int Pathlength) {
        if (str[Pathlength] == '\0')
            return true;
        if (i < 0 || i >=  rows || j <0 || j >=  cols || matrix[i * cols + j] != str[Pathlength] || visited[i * cols + j])
            return false;

        //解空间里面进行回溯,递归过程
        visited[i * cols + j] = true;
        bool flag = dfs(matrix, rows, cols, str, i - 1, j, visited, Pathlength + 1) ||
                    dfs(matrix, rows, cols, str, i, j - 1, visited, Pathlength + 1) ||
                    dfs(matrix, rows, cols, str, i + 1, j, visited, Pathlength + 1) ||
                    dfs(matrix, rows, cols, str, i, j + 1, visited, Pathlength + 1);
        visited[i * cols + j] = false;

        return flag;
    }
};

leecode

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        if(board.empty()||word.empty())
            return false;
        //声明变量
        int rows = board.size();
        int cols = board[0].size();
        vector<vector<bool> > visited(rows,vector<bool>(cols,false));
        //从某一点出发,如果从该点出发能找到路径,则返回true,否则遍历矩阵中的每一个点(作为根节点)
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {	//根节点
                if (dfs(board, rows, cols, word, i, j, visited, 0))
                    return true;    //有一条目标路径即返回true,相当于第一次剪枝
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>&board, int rows, int cols, string word, int i, int j, vector<vector<bool>>&visited, int id){
        //递归终止
        if (i < 0 || i >= rows || j < 0 || j >= cols || visited[i][j] == true)//越界处理或者已经访问过     相当于第二次剪枝
            return false;
        if( board[i][j] != word[id])    //当前访问的节点不等于word中对应的字符,即当前访问的节点不在路径中   相当于第三次剪枝
            return false;
        if(id == word.size()-1)	//相当于第四次剪枝
            return true;


        //当前访问的节点在路径中则进入递归,直至递归终止条件停止递归
        visited[i][j] = true;
        bool flag = dfs(board, rows, cols, word, i - 1, j, visited, id + 1) ||  //左
                    dfs(board, rows, cols, word, i + 1, j, visited, id + 1) ||  //上
                    dfs(board, rows, cols, word, i, j + 1, visited, id + 1) ||  //右
                    dfs(board, rows, cols, word, i, j - 1, visited, id + 1);    //下
        visited[i][j] = false;
        //回溯返回值
        return flag;

    }

};

20200523将leecode代码移植到到牛客

class Solution {
public:
    bool hasPath(char* matrix, int rows, int cols, char* str){
        //越界处理
        if (matrix == nullptr || rows <= 0 || cols <= 0 || str == nullptr)
            return false;
 
        //一维数组转换为二维数组(开二维数组)
        //int[][] board = new int [rows][cols];c++没有二维数组的这个数据类型,故此处这种写法错误
        char** board = new char* [rows];
        for(int i = 0; i < rows; ++i)
            board[i] = new char [cols];
 
        int row = 0, col = 0;
        for(int i = 0; i < rows*cols; ++i ){
            if(col >= cols){
                row++;
                col = 0;
            }
            board[row][col] = matrix[i];
            ++col;
        }
        //char* 转换为string
        string word = str;
 
        vector<vector<bool> > visited(rows,vector<bool>(cols,false));
 
        //从某一点出发,如果从该点出发能找到路径,则返回true,否则遍历矩阵中的每一个点(作为根节点)
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) { //根节点
                if (dfs(board, rows, cols, word, i, j, visited, 0))
                    return true;    //有一条目标路径即返回true
            }
        }
 
        //释放空间
        for(int i = 0; i < rows; ++i)
            delete[] board[i];
        delete[] board;
 
 
        return false;
    }
 
 
 
    bool dfs(char** board, int rows, int cols, string word, int i, int j, vector<vector<bool>>&visited, int id){
        //递归终止
        if (i < 0 || i >= rows || j < 0 || j >= cols || visited[i][j] == true )//越界处理或者已经访问过
            return false;
        if( board[i][j] != word[id])    //当前访问的节点不等于word中对应的字符,即当前访问的节点不在路径中
            return false;
        if(id == word.size()-1)
            return true;
 
 
        //当前访问的节点在路径中则进入递归,直至递归终止条件停止递归
        visited[i][j] = true; //位置的标记
        bool flag = dfs(board, rows, cols, word, i - 1, j, visited, id + 1) ||  //左
                    dfs(board, rows, cols, word, i + 1, j, visited, id + 1) ||  //上
                    dfs(board, rows, cols, word, i, j + 1, visited, id + 1) ||  //右
                    dfs(board, rows, cols, word, i, j - 1, visited, id + 1);    //下
        visited[i][j] = false;//状态的回溯
        
        return flag;
    }
};
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值