剑指 Offer 12(图搜索篇1).矩阵中的路径

剑指 Offer 12(图搜索篇1).矩阵中的路径

问题描述:

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

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

image-20210825210750330

示例:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

解题思路:参考思路

1,回溯算法加深度优先求解

回溯算法实际上一个类似枚举的搜索尝试过程,也就是一个个去试,我们解这道题也是通过一个个去试,下面就用示例1来画个图看一下

image-20210825215630517

我们看到他是从矩形中的一个点开始往他的上下左右四个方向查找,这个点可以是矩形中的任何一个点,所以代码的大致轮廓我们应该能写出来,就是遍历矩形所有的点,然后从这个点开始往他的4个方向走,因为是二维数组,所以有两个for循环,代码如下:

 public boolean exist(char[][] board, String word) {
        //将字符串String word转变为char型数组
        char[] chars = word.toCharArray();
        //建立一个与矩阵(二维数组)同等长度的布尔型数组,用于探究矩阵数组中的元素是否使用
        boolean[][] isVisited = new boolean[board.length][board[0].length];
        //安全性检验
        if(board == null || board[0] == null || board.length == 0 || 
        board[0].length == 0 || word == null || word.equals("")){
            return false;
        }
        //遍历矩阵中的元素,找到与word数组中的第一个元素相同的元素
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){
                if(board[i][j] == chars[0]){
                    //找到对应的元素时,通过广度优先搜索获取与前一个元素相邻元素,
                    //判断与word数组的下一个元素是否相同
                    if(dfs(board,i,j,isVisited,chars,0)){
                        return true;
                    }
                   
                }
            }
        }
        return false;
    }

这里关键代码是dfs这个函数,因为每一个点我们都可以往他的4个方向查找,所以我们可以把它想象为一棵4叉树,就是每个节点有4个子节点,而树的遍历我们最容易想到的就是递归,我们来大概看一下:

 private boolean dfs(char[][] board,int i,int j,boolean[][] isVisited,char[] chars,int index){
 
        //安全性检验,最重要的是判断该元素是否已经被搜索
        if(i < 0 || j < 0 || i >= board.length || j >= board[0].length 
        || isVisited[i][j] || board[i][j] != chars[index]){
            return false;
        }

        //当在board元素中找到与word数组相对应的元素
        if(board[i][j] == chars[index]){
            //当元素索引为chars.length - 1时,代表已经搜索完word数组中的元素,返回true
            if(index == chars.length - 1){
                return true;
                }else{
                    //先将对应元素标记为已搜索
                    isVisited[i][j] = true;
                    //找到对应元素相邻元素
                    boolean res = dfs(board,i + 1,j,isVisited,chars,index + 1) 
                    ||dfs(board,i,j + 1,isVisited,chars,index + 1)
                    ||dfs(board,i - 1,j,isVisited,chars,index + 1) 
                    ||dfs(board,i,j - 1,isVisited,chars,index + 1);
                    //将对应元素的标记复原为未搜索
                    isVisited[i][j] = false;
                    return res;
                }
           
        }
        return false;

    }

回溯又是如何解决这个问题的呢,要想弄懂回溯我们首先要搞懂递归,递归分为两步,先是递,然后才是归。当我们沿着当前坐标往下传递的时候,我们可以把当前坐标的值修改,然后回归到当前坐标的时候再把当前坐标的值复原,这就是回溯的过程。我们来看下代码,比上面简洁了好多,运行效率也会有很大的提升。

利用回溯加深度优先搜索

class Solution {
    public boolean exist(char[][] board, String word) {
        //将字符串String word转变为char型数组
        char[] chars = word.toCharArray();
        //建立一个与矩阵(二维数组)同等长度的布尔型数组,用于探究矩阵数组中的元素是否使用
        boolean[][] isVisited = new boolean[board.length][board[0].length];
        //安全性检验
        if(board == null || board[0] == null || board.length == 0 || 
        board[0].length == 0 || word == null || word.equals("")){
            return false;
        }
        //遍历矩阵中的元素,找到与word数组中的第一个元素相同的元素
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){
                if(board[i][j] == chars[0]){
                    //找到对应的元素时,通过广度优先搜索获取与前一个元素相邻元素,
                    //判断与word数组的下一个元素是否相同
                    if(dfs(board,i,j,isVisited,chars,0)){
                        return true;
                    }
                   
                }
            }
        }
        return false;
    }
    //建立广度优先搜索方法
    private boolean dfs(char[][] board,int i,int j,boolean[][] isVisited,char[] chars,int index){
 
        //安全性检验,最重要的是判断该元素是否已经被搜索
        if(i < 0 || j < 0 || i >= board.length || j >= board[0].length 
        || isVisited[i][j] || board[i][j] != chars[index]){
            return false;
        }

        //当在board元素中找到与word数组相对应的元素
        if(board[i][j] == chars[index]){
            //当元素索引为chars.length - 1时,代表已经搜索完word数组中的元素,返回true
            if(index == chars.length - 1){
                return true;
                }else{
                    //先将对应元素标记为已搜索
                    isVisited[i][j] = true;
                    //找到对应元素相邻元素
                    boolean res = dfs(board,i + 1,j,isVisited,chars,index + 1) 
                    ||dfs(board,i,j + 1,isVisited,chars,index + 1)
                    ||dfs(board,i - 1,j,isVisited,chars,index + 1) 
                    ||dfs(board,i,j - 1,isVisited,chars,index + 1);
                    //将对应元素的标记复原为未搜索
                    isVisited[i][j] = false;
                    return res;
                }
           
        }
        return false;

    }
}

解题思路2:参考链接

本问题是典型的矩阵搜索问题,可使用 深度优先搜索(DFS)+ 剪枝 解决。

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

image-20210826134103920

DFS 解析:

  • 递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
  • 终止条件:返回 false : (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。返回 truetrue : k = len(word) - 1 ,即字符串 word 已全部匹配。
  • 递推工作:
  1. 标记当前矩阵元素: 将 board[i] [j] 修改为 空字符 ‘’ ,代表此元素已访问过,防止之后搜索时重复访问。(在java中空字符表示为’\0’)
  2. 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
  3. 还原当前矩阵元素: 将 board[i] [j] 元素还原至初始值,即 word[k] 。
  4. 返回值: 返回布尔量 res ,代表是否搜索到目标字符串。
class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        if(board == null || board.length == 0 || board[0].length == 0 
        || words == null || words.length == 0){
            return false;
        }
        int index = 0;
        for(int i = 0 ;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){
                if(board[i][j] == words[0]){
                    if(dfs(board,i,j,words,index)){
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean dfs(char[][] board,int i,int j,char[] words,int index){
        //安全性检验,board[i][j] != words[index]
        if(i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != words[index]){
            return false;
        }
        if(index == words.length - 1){
            return true;
        }else{
             //将对应矩阵中的元素标记为空
            board[i][j] = '\0';
            boolean res = dfs(board,i + 1,j,words,index + 1) 
            ||dfs(board,i,j + 1,words,index + 1)
            ||dfs(board,i - 1,j,words,index + 1) 
            ||dfs(board,i,j - 1,words,index + 1);
            //将board[i][j]对应元素复原
            board[i][j] = words[index];
            return res;
        }
       
        
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原来如此呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值