题目
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
思路
小卡小课堂:DFS和回溯的区别
很多小伙伴分不清这两个的区别,网上也有很多专业的解释,这里我说一下我的理解:
- 回溯是一种更通用的算法,回溯是DFS的一般形式
- DFS是与搜索树/图结构相关的特定回溯形式,DFS是回溯的特殊形式
- DFS扩展到一般问题,就变成了回溯;回溯使用在树/图问题上,就是DFS
本题是用回溯法/DFS+剪枝(经过上面的解释,在本题的树/图问题中,回溯就是DFS)
算法思想 设函数 check(i,j,k)
表示判断以网格的 (i,j)
位置出发,能否搜索到单词 word[k]
,其中k
为单词下标索引, word[k]
表示字符串 word
的第 k
个字符。如果能搜索到,则返回 true
,反之返回 false
。函数 check(i,j,k)
的执行步骤如下:
- 如果
board[i][j]≠s[k]
,当前字符不匹配,直接返回false
- 如果当前已经访问到字符串的末尾,即
k == s.length()-1
,,且对应字符依然匹配,此时直接返回true
- 否则,遍历当前位置的所有相邻位置。如果从某个相邻位置出发,能够搜索到
word[k+1]
,则返回true
,否则返回false
。 - 这样,对每一个位置
(i,j)
都调用函数check(i,j,0)
进行检查:只要有一处返回true
,就说明网格中能够找到相应的单词,否则说明不能找到。 - 为了防止重复遍历相同的位置,需要额外维护一个与
board
等大的visited
数组,用于标识每个位置是否被访问过。每次遍历相邻位置时,需要跳过已经被访问的位置。
java代码如下:
class Solution{
public boolean exist(char[][] board, String word){
int h = board.length, w = board[0].length;
boolean[][] visited = new boolean[h][w];
for(int i = 0; i < h; i++){
for(int j = 0; j < w; j++){
boolean flag = check(board, visited, i, j, word, 0);
if(flag){
return true;
}
}
}
return false;
}
public boolean check(char[][] board, boolean[][] visited, int i, int j, String s, int k){
if(board[i][j] != s.charAt(k)){//如果当前位置不匹配直接返回false
return false;
} else if ( k == s.length() -1){//如果匹配完毕,则返回true
return true;
}
visited[i][j] = true;//标记当前位置已访问过
int[][] directions = {{0,1}, {0,-1}, {1,0}, {-1,0}};//定义遍历的四个方向
boolean result = false;//记录最终结果
for(int[] dir : directions){
int newi = i + dir[0], newj = j + dir[1];//更新新的坐标
if(newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length){//如果位置合法
if(!visited[newi][newj]){//且该位置未被访问过
boolean flag = check(board, visited, newi, newj, s, k + 1);//检查新位置,并检查匹配k+1是否成功
if(flag){
result = true;
break;//找到一个匹配的位置即可进行下一轮位置循环
}
}
}
}
//如果for循环没有找到合法的位置或者匹配成功的位置,则回退到之前位置
visited[i][j] = false;
return result;
}
}