大家好,我是程序员吴师兄,欢迎来到 图解剑指 Offer 结构化专栏,在这个专栏里我将和大家一起学习如何用结构化的思维来思考、解题、写代码,希望能帮助你即使在面试的时候紧张也能做对。
今天分享的题目来源于的剑指 Offer 系列 面试题 12. 矩阵中的路径。
题目汇总链接:https://www.algomooc.com/hi-offer
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"], ["s","f","c","s"], ["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 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
提示:
1 <= board.length <= 2001 <= board[i].length <= 200我们依旧用 四步分析法 进行结构化的分析。
首先看一下矩阵的初始状态。

我们需要在这个矩阵中寻找目标字符串 bfce,第一步要做的就是先匹配上目标字符串的第一个元素 b ,我们从矩阵的第一行第一列的元素开始匹配,找到了 a 。

目标字符串为 bfce,此时查找第一个元素为 a ,与目标字符串的第一个元素 b 不相同,需要在四个方向搜索,看看能不能找到符合要求的元素,我们按照上左下右的顺序进行遍历寻找。





为了保证不重复访问节点,可以将这条路径上已经访问过的节点,修改为不在 word 当中的一个字符,保证以后再次访问时不会重复访问,这里我们将其修改为特殊字符 # 。

修改完后会出现一种情况,当前的节点元素与目标元素相匹配,但是在它的四个方向的节点中都找不到可以匹配到目标下一元素的节点。
比如此时当前元素 c 与目标元素 c 相匹配,但是目标下一元素为 x,而在当前元素的四个方向上都找不到 x ,需要把这个点回退,根据之前的操作,当前的节点被修改为了 #,所以为了能够回退成功,再回退操作时需要重新将 # 修改回原来的元素。

本题提供了一个矩阵,矩阵是一个二维数组,需要我们在二维数组中进行搜索,为了能够覆盖所有的情况,必然要使用两个嵌套的循环。
在搜索过程中,当遇到匹配成功的元素,搜索其下一元素的操作与当前的操作一致,即可以使用递归。
board 中的行列索引 i 和 j ,当前目标字符在word 中的索引 k 。k = len(word) - 1 ,即字符串 word 已全部匹配。board[ i ] [ j ] 修改为特殊字符 # ,代表此元素已访问过,防止之后搜索时重复访问。board[ i ] [ j ] 元素还原至初始值,即 word[k] 。res ,代表是否搜索到目标字符串。
剑指 Offer 12. 矩阵中的路径.002

剑指 Offer 12. 矩阵中的路径.003

剑指 Offer 12. 矩阵中的路径.004

剑指 Offer 12. 矩阵中的路径.005

剑指 Offer 12. 矩阵中的路径.006

剑指 Offer 12. 矩阵中的路径.007

剑指 Offer 12. 矩阵中的路径.008

剑指 Offer 12. 矩阵中的路径.009

剑指 Offer 12. 矩阵中的路径.010

剑指 Offer 12. 矩阵中的路径.011

剑指 Offer 12. 矩阵中的路径.012

剑指 Offer 12. 矩阵中的路径.013

剑指 Offer 12. 矩阵中的路径.014

剑指 Offer 12. 矩阵中的路径.015

剑指 Offer 12. 矩阵中的路径.016

剑指 Offer 12. 矩阵中的路径.017

剑指 Offer 12. 矩阵中的路径.018

剑指 Offer 12. 矩阵中的路径.019

剑指 Offer 12. 矩阵中的路径.020

剑指 Offer 12. 矩阵中的路径.021

剑指 Offer 12. 矩阵中的路径.022

剑指 Offer 12. 矩阵中的路径.023

剑指 Offer 12. 矩阵中的路径.024

剑指 Offer 12. 矩阵中的路径.025

剑指 Offer 12. 矩阵中的路径.026

剑指 Offer 12. 矩阵中的路径.027

剑指 Offer 12. 矩阵中的路径.028

剑指 Offer 12. 矩阵中的路径.029

剑指 Offer 12. 矩阵中的路径.030

剑指 Offer 12. 矩阵中的路径.031

剑指 Offer 12. 矩阵中的路径.032
// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
class Solution {
public 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++) {
// 以该元素为起始点,递归检查是否符合要求
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
// 边界情况判断
// 行越界
// 列越界
// 矩阵元素已访问过
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
// 之前已经和目标字符串匹配成功了 length - 1 个字符,此时又匹配成功了最后一个元素,直接返回结果
if(k == word.length - 1) return true;
// 标记当前矩阵元素,将其修改为特殊字符 # ,代表此元素已访问过,防止之后搜索时重复访问。
board[i][j] = '#';
// 搜索元素的四个方向 上 左 下 右,匹配下一个目标元素
boolean res = dfs( board , word , i , j - 1 , k + 1 )
|| dfs( board , word , i - 1 , j , k + 1 )
|| dfs( board , word , i , j + 1 , k + 1 )
|| dfs( board , word , i + 1 , j , k + 1 );
// 回退时还原当前矩阵元素
board[i][j] = word[k];
// 返回结果
return res;
}
}
注意 :搜索元素的四个方向 上 左 下 右,匹配下一个目标元素时的代码不要拆分,否则会出现超出时间限制,因为这样做相当于每一个方向都进行了搜索,而事实上不需要,只需要找到一条符合要求的路径即可。
boolean up = dfs(board, word, i, j - 1, k + 1);
boolean left = dfs(board, word, i - 1, j , k + 1);
boolean down = dfs(board, word, i, j + 1, k + 1);
boolean right = dfs(board, word, i + 1, j , k + 1);
boolean res = up || left || down || right;

时间复杂为 O( 4k M N ),其中 M,N 分别为矩阵行列长度, K 为字符串 word 的长度,每个字符都有 4 个方向。
空间复杂度为 O(K) 。
1、https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/solution/mian-shi-ti-12-ju-zhen-zhong-de-lu-jing-shen-du-yo/
2、https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/
3、https://www.algomooc.com