剑指 Offer 每日一题 | 9、矩阵中的路径

567 阅读3分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

一、前言

大家好,本文章属于《剑指 Offer 每日一题》中的系列文章中的第 9 篇。

在该系列文章中我将通过刷题练手的方式来回顾一下数据结构与算法基础,同时也会通过博客的形式来分享自己的刷题历程。如果你刚好也有刷算法题的打算,可以互相鼓励学习。我的算法基础薄弱,希望通过这两三个月内的时间弥补这块的漏洞。本次使用的刷题语言为 Java ,预计后期刷第二遍的时候,会采用 Python 来完成。

二、题目

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

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

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

image-20210816192252576

示例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

三、解题

本题是一个可以用回溯法解决的经典题。

回溯算法实际上是 类似于枚举的搜索尝试过程,也就是一个个去尝试,例如下面这张图。

image.png

可以看到从矩阵的一个点开始会往它的上下左右四个方向查找,这个点可以是矩阵的任何一点。

逐步递归,可以把它想象成一个四叉树。这就是深度遍历算法。

从这个图中,我们要找 A->B->C->C->D 的过程是这样的:

  • 1、先找到 A点,然后,遍历它的 “子树” ,也就是上下左右,发现 S 走不通,于是就只能走 B。
  • 2、找到 B,同样,找他的 上下左右 四个节点,找到 C
  • 3、依次类推,如果没找到就退回上一个节点尝试去找,这其实也就是递归的应用。

需要注意的是,如果某个点被访问了,我们需要把它设为已访问,不然可能出现死循环的状态。同时标记字符 为已访问状态也是为了防止字符与矩阵原有字符重复。当然,递归出来之后,我们要把字符还原至初始值。

3.1 代码

 public class Number9 {
     public static void main(String[] args) {
         char[][] board = new char[][]{{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
         String word = "ABCCED";
         System.out.println(exist(board,word));
 ​
     }
     public static boolean exist(char[][] board, String word) {
         char[] words = word.toCharArray();
         for (int i = 0; i < board.length; i++) {
             for (int j = 0; j < board[0].length; j++) {
                 //从[i,j]这个坐标开始查找
                 if (dfs(board, words, i, j, 0))
                     return true;
             }
         }
         return false;
     }
 ​
     static boolean dfs(char[][] board, char[] word, int i, int j, int index) {
         //边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
         //如果这个字符不等于board[i][j],说明验证这个坐标路径是走不通的,直接返回false
         if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[index])
             return false;
         //如果word的每个字符都查找完了,直接返回true
         if (index == word.length - 1)
             return true;
         //把当前坐标的值保存下来,为了在最后复原
         char tmp = board[i][j];
         //然后修改当前坐标的值
         board[i][j] = '#';
         //走递归,沿着当前坐标的上下左右4个方向查找
         boolean res = dfs(board, word, i + 1, j, index + 1)
                 || dfs(board, word, i - 1, j, index + 1)
                 || dfs(board, word, i, j + 1, index + 1)
                 || dfs(board, word, i, j - 1, index + 1);
         //递归之后再把当前的坐标复原
         board[i][j] = tmp;
         return res;
     }
 }

3.2 运行效果

image-20210816191310666

3.3 复杂度分析

  • 时间复杂度:矩阵共有 M*N 个点,时间复杂度 为O(MN)
  • 空间复杂度:栈空间使用为递归的深度。

四、总结

本题考察了二维数组中的回溯应用,回溯法通常用来解决迷宫类问题,查找路径,本题也将数组抽象成四叉树,来方便我们理解,回溯法的经典题,必须掌握。

今天的刷题就到这了,欢迎点赞评论交流,剑指 Offer 刷题之旅将继续展开!