单词搜索
题目链接:https://leetcode-cn.com/problems/word-search/
题目描述:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
DFS :
算法1:对每一个可能的点采用DFS遍历,判断是否可以得到唯一解,若存在即返回true,否则为 false。其中在递归过程中,flag 数组的值需要在递归之前置为 true,递归之后置为 flase,即它的状态需要回退。参考代码如下(带注释):
int[] addi={1,-1,0,0};
int[] addj={0,0,1,-1};
boolean[][] flag;
public boolean exist(char[][] board, String word) {
int m=board.length,n=board[0].length;
flag=new boolean[m][n];//flag数组用来标记某个字符是否被访问过
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(board[i][j]==word.charAt(0)){//对每一个首字符都需要进行DFS遍历,判断是否在此处可以搜索到到完整的字符串
if(solve(board,i,j,word,1))
return true;
}
}
}
return false;
}
/*想一想,在每次调用 slove 方法时,flag 数组的所有值应该都为 false 吧,即上一次 solve 方法对 flag 造成的
影响和下一次 solve 方法调用 flag 无关, flag 的功能仅是为了在一次遍历中不要重复访问已访问过的字符。
一次不成功的搜索之后,访问过的元素(flag 值为 true)应该需要再次置为 false。在下一次搜索时应该还可以访问
这些元素,也就是说,flag 数组的值有一个回退的动作,*/
boolean solve(char[][] board,int i,int j,String word,int count){
if(count==word.length())//退出条件
return true;
flag[i][j]=true; //flag 中的当前元素以访问,置为 true
boolean re=false;//保存退出返回值
for(int k=0;k<4;k++){
int newI=i+addi[k],newJ=j+addj[k];
if(newI<0||newJ<0||newI>=board.length||newJ>=board[0].length||flag[newI][newJ]==true)
continue;
if(board[newI][newJ]==word.charAt(count))//一种可能成功的情况
re=re||solve(board,newI,newJ,word,count+1);//这里为或操作
}
flag[i][j]=false; //flag 重新恢复为false
return re;
}
算法2:另外一种描述方法如下(添加一个 boolean 变量 ans 来判断是否搜索到字符串):
int[] addi={1,-1,0,0};
int[] addj={0,0,1,-1};
boolean[][] flag;
boolean ans;
public boolean exist(char[][] board, String word) {
int m=board.length,n=board[0].length;
flag=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)){
solve(board,i,j,word,1);
if(ans==true)
return true;
}
}
}
return false;
}
void solve(char[][] board,int i,int j,String word,int count){
if(count==word.length()){
ans=true;
return;
}
flag[i][j]=true;
for(int k=0;k<4;k++){
int newI=i+addi[k],newJ=j+addj[k];
if(newI<0||newJ<0||newI>=board.length||newJ>=board[0].length||flag[newI][newJ]==true)
continue;
if(board[newI][newJ]==word.charAt(count))
solve(board,newI,newJ,word,count+1);
}
flag[i][j]=false;
}
小小的优化
算法1 和 2 都还需要优化,它们都有同一个问题,在搜索到字符串之后不会立即返回,这样会浪费时间的。算法1 改进如下:
for(int k=0;k<4;k++){
int newI=i+addi[k],newJ=j+addj[k];
if(newI<0||newJ<0||newI>=board.length||newJ>=board[0].length||flag[newI][newJ]==true)
continue;
if(board[newI][newJ]==word.charAt(count)){
re=re||solve(board,newI,newJ,word,count+1);
if(re)
break;
}
}
一旦搜索到字符串之后,此时re 为 true,短路 || 不会执行 solve(board,newI,newJ,word,count+1) ,但是也应该加上 break 语句让程序执行跳出 for 循环,直接退出返回正确的结果。相应的也可对算法 2 进行优化。
# 总结 深度优先遍历是回溯算法使用的策略。