题目:
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false
分析:
该题的本质就是在这个二维数组中,从某个位置的元素出发,寻找一个连通分量,然后判断按照这个连通分量搜索的顺序构成的一个字符串是否和给定的的单词相同,不同则一步一步的回溯,分别判断能否寻找到与给定单词相同的一串字符串。具体的操作如下:
首先,遍历这个二维数组,当目前正在遍历的字符和给定字符串首字符相同时,那么就开始进行深度搜索;
深度搜索时,要判断当前这个位置的字符是否被访问过,如果访问过则返回false;判断当前遍历的字符和给定字符串中对应位置的字符是否相等,这个“对应位置”我们是这样计算的,初始遍历时,使用一个变量k记录搜索字符串的长度,初始值为0,这个变量的值也正好和我们要当前要判断的给定字符串中第几个字符相一致,比如k=0时,此时我们刚开始进行搜索,遍历的字符正好是此次遍历的起始点,我们要判断当前遍历的字符和给定字符串中的第0个字符是否相等, 如果不相等则直接返回false,相等则继续向下搜索。
向下搜索,主要可选的范围依旧是四个,即上下左右四个方向,所以要通过for循环遍历四个方向,确保回溯时选择其他方向。然后先选择某个方向向下搜索,如果这个方向走下去可以找到最终结果,那么就直接返回true,只要当k和给定字符串长度相等时,说明找到了最终结果,此时返回true,那么递归返回时,一路都是返回true。否则回溯回来,选择其他方向继续向下搜索
其中需要注意的是,当某个点在可选择的四个方向都走完依旧不能得到结果时,说明“此路不通”,那么就要向上回溯,选择其父亲节点的其他邻接子节点,回溯的时候,要将此点对应的访问标记数组值设置为false,方便以后走其他方向时可以继续访问此点。
代码:
private int[][] dirs;
private int m;
private int n;
private ArrayList<String> res;
public boolean exist(char[][] board, String word) {
m = board.length;
n = board[0].length;
dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
StringBuffer tempStr = new StringBuffer();
res = new ArrayList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == word.charAt(0)){
res.clear();
boolean[][] isVisited = new boolean[m][n];
findwordByDFS(0,tempStr,word,board,i,j,isVisited);
if (res.size()!=0){
return true;
}
}
}
}
return false;
}
private void findwordByDFS(int k, StringBuffer tempStr, String word, char[][] board, int i, int j,boolean[][] isVisited) {
if (tempStr.length() == word.length()){
if (tempStr.toString().equals(word)){
res.add(tempStr.toString());
}
return;
}
if (i < 0 || i >= m || j < 0 || j >= n || isVisited[i][j] || word.charAt(k) != board[i][j]){
return;
}
isVisited[i][j] = true;
tempStr.append(board[i][j]);
if (tempStr.toString().equals(word)){
res.add(tempStr.toString());
return;
}
for (int[] dir : dirs) {
int cur_i = dir[0] + i;
int cur_j = dir[1] + j;
if ((cur_i < 0 || cur_i >= m || cur_j < 0 || cur_j >= n)){
continue;
}
if (!isVisited[cur_i][cur_j]){
findwordByDFS(k+1,tempStr,word,board,cur_i,cur_j,isVisited);
}
}
isVisited[i][j] = false;
tempStr.deleteCharAt(k);
}
第一次写的有些冗余,自己测试时都能够正确通过,但是在力扣上提交的时候,超时了,说明程序性能太差了,分了一下,上面这个程序,当搜索到最终结果的时候,还是将最终结果添加到列表的时候,向上返回后,并不是一直向上返回然后停止此次运行,而是继续走向上回溯一层,继续按照其他方向搜索,这就浪费了大量的时间,所以就优化了一下,一旦找到最终结果,就一路向上返回,停止此次运行:
private int[][] dirs;
private int m;
private int n;
public boolean exist(char[][] board, String word) {
m = board.length;
n = board[0].length;
dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
boolean[][] isVisited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == word.charAt(0) && findwordByDFS(0,word,board,i,j,isVisited)){
return true;
}
}
}
return false;
}
private boolean findwordByDFS(int k, String word, char[][] board, int i, int j,boolean[][] isVisited) {
if (k == word.length()){
return true;
}
if (i < 0 || i >= m || j < 0 || j >= n || isVisited[i][j] || word.charAt(k) != board[i][j]){
return false;
}
isVisited[i][j] = true;
for (int[] dir : dirs) {
int cur_i = dir[0] + i;
int cur_j = dir[1] + j;
if (findwordByDFS(k+1,word,board,cur_i,cur_j,isVisited)){
return true;
}
}
isVisited[i][j] = false;
return false;
}