剑指 Offer 12(图搜索篇1).矩阵中的路径
问题描述:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
输入: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来画个图看一下
我们看到他是从矩形中的一个点开始往他的上下左右四个方向查找,这个点可以是矩形中的任何一个点,所以代码的大致轮廓我们应该能写出来,就是遍历矩形所有的点,然后从这个点开始往他的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 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
- 剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝 。
DFS 解析:
- 递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
- 终止条件:返回 false : (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。返回 truetrue : k = len(word) - 1 ,即字符串 word 已全部匹配。
- 递推工作:
- 标记当前矩阵元素: 将 board[i] [j] 修改为 空字符 ‘’ ,代表此元素已访问过,防止之后搜索时重复访问。(在java中空字符表示为’\0’)
- 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
- 还原当前矩阵元素: 将 board[i] [j] 元素还原至初始值,即 word[k] 。
- 返回值: 返回布尔量 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;
}
}
}