【LeetCode剑指offer 02】矩阵中的路径(老鼠走迷宫plus,应用深度优先搜索与回溯机制)

发布时间 2023-04-05 23:03:52作者: dayceng

矩阵中的路径

https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。

img

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:

输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

提示:

m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board 和 word 仅由大小写英文字母组成

思路

这题其实就是经典的算法题“老鼠走迷宫”的变体,在老鼠走迷宫中,我们要避开障碍,找到通往出口的路径

这里也一样,只不过"障碍"变成了非目标字符,"通往出口的路径"就是我们的目标字符串

参考走迷宫的解法,我们要逐层遍历矩阵(迷宫),同时判断当前遍历值的下一步遍历位置是否满足要求,不满足要求就要换方向走,直到找到满足要求的方向再继续遍历(前进)

Picture0.png

借一下k神的,上面描述的过程即如图中所示

一般来说,知道老鼠走迷宫的,想出本题的解题思路应该不难,但是代码实现就有点难写

代码

实现上,根据分析,肯定是需要使用回溯机制的,也就是要用递归去实现(本题就是经典的深度优先搜索题)

老规矩,按照三部曲去分析一下

代码分析
1、确定递归函数参数与返回值

因为需要对不同前进方向的状态进行确认,因此递归函数需要返回值,且类型为布尔类型

需要的参数有:待搜索的矩阵board、目标字符串word、前进方向i、j,以及当前目标字符在word中的索引k

注意点:

1、前进方向i、j是需要一个约束边界的,因此还需定义两个变量来保存矩阵的行列大小

2、这里我们需要引入一个变量k来标记当前遍历值(如果是目标字符串中的字符)位于目标字符串中的下标,这样才可以避免出现重复使用某个值的情况

class Solution {
private:
    //定义变量保存数组的行列
    int rows, cols;
    //确定递归函数的参数与返回值
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k){
        
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        

    }
};
2、确定终止条件

终止条件有两类:非法情况和搜索完成情况

非法情况

即遍历过程中出现的各种非法情况,以及当前遍历值不是目标字符的情况

例如当前遍历值不是要检查上下左右四个方向哪边能继续走嘛,万一某个方向超出矩阵大小范围,这就属于一类触发递归终止的条件

如果遍历值都没出现在目标字符串word中,就直接不用往该方向走了

搜索完成

k用于指示当前找到了目标字符串中的哪个字符,如果k指向word的最后一个字符,那么意味着搜索结束,返回true

class Solution {
private:
    //定义变量保存数组的行列
    int rows, cols;
    //确定递归函数的参数与返回值
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k){
         //首先确定返回false的情况
        //i(行)、j(列)越界、当前矩阵遍历值不是目标字符串中的字符
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        //然后确定返回true的情况
        //k用于记录当前为目标字符的遍历值在目标字符串中的位置,当k以及在目标字符串末尾,就说明所有字符均已找到
        if(k == word.size() - 1) return true;
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        

    }
};
3、处理单层逻辑

在当前递归层中,我们需要先将当前遍历值填充为'0'(其实就是初始化一下矩阵,因为还不知道这个新的位置是否满足条件)

然后调用递归函数dfs去当前遍历值的四个方向(上下左右)去判断哪个方向可以走下一步,直到找到可以走的方向,并把当前遍历值位置的值改为对应目标字符串中k位置的字符,然后重复上述流程,判断下一个遍历位置是否满足条件

class Solution {
private:
    //定义变量保存数组的行列
    int rows, cols;
    //确定递归函数的参数与返回值
    //递归函数中,需要对不同前进方向的状态进行确认,因此返回值是布尔类型
    //需要的参数有:待搜索的矩阵board、目标字符串word、前进方向i、j,以及当前目标字符在word中的索引k
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k){
        //首先确定返回false的情况
        //i(行)、j(列)越界、当前矩阵遍历值不是目标字符串中的字符
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        //然后确定返回true的情况
        //k用于记录当前为目标字符的遍历值在目标字符串中的位置,当k以及在目标字符串末尾,就说明所有字符均已找到
        if(k == word.size() - 1) return true;

        //先当前遍历值统一填充为0(因为还不知道这个新的位置是否满足条件)
        board[i][j] = '0';
        //递归调用dfs对当前遍历值的下一个位置进行判断(上下左右)
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) 
                    || dfs(board, word, i, j - 1, k + 1) || dfs(board, word, i, j + 1, k + 1);
        //如果四个方向中有一个走得通(即返回true),那就把矩阵中当前位置的字符修改为目标字符串中k指向的相应字符
        //该操作是在最深的一层递归中完成的
        board[i][j] = word[k];

        return res;//向上一层递归返回本层递归结果

    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        
    }
};
完整代码

主函数中,需要获取矩阵的行列(别搞混)

然后使用两层for循环遍历矩阵,调用递归判断结果,如果递归函数返回的是true,那么就结束

如果for遍历结束都没返回,那就返回false

class Solution {
private:
    //定义变量保存数组的行列
    int rows, cols;
    //确定递归函数的参数与返回值
    //递归函数中,需要对不同前进方向的状态进行确认,因此返回值是布尔类型
    //需要的参数有:待搜索的矩阵board、目标字符串word、前进方向i、j,以及当前目标字符在word中的索引k
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k){
        //首先确定返回false的情况
        //i(行)、j(列)越界、当前矩阵遍历值不是目标字符串中的字符
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        //然后确定返回true的情况
        //k用于记录当前为目标字符的遍历值在目标字符串中的位置,当k以及在目标字符串末尾,就说明所有字符均已找到
        if(k == word.size() - 1) return true;

        //先当前遍历值统一填充为0(因为还不知道这个新的位置是否满足条件)
        board[i][j] = '0';
        //递归调用dfs对当前遍历值的下一个位置进行判断(上下左右)
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) 
                    || dfs(board, word, i, j - 1, k + 1) || dfs(board, word, i, j + 1, k + 1);
        //如果四个方向中有一个走得通(即返回true),那就把矩阵中当前位置的字符修改为目标字符串中k指向的相应字符
        //该操作是在最深的一层递归中完成的
        board[i][j] = word[k];

        return res;//向上一层递归返回本层递归结果
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        //获取矩阵的行列长度
        rows = board.size();
        cols = board[0].size();

        //遍历矩阵
        for(int i = 0; i < rows; ++i){
            for(int j = 0; j < cols; ++j){
                if(dfs(board, word, i, j, 0)) return true;//调用dfs判断目标字符串在矩阵中是否存在,k初始值为0
            }
        }
        return false;
    }
};