一个极其普通的解法
主要思想是找到可行的起始点 深度遍历搜索 保存当前的字符串如果匹配则继续否则剪枝
class Solution {
char[][] board;
boolean res = false;
int n;
int m;
boolean[][] visited;
public boolean exist(char[][] board, String word) {
this.board = board;
n = board.length;
m = board[0].length;
visited = new boolean[n][m];
char c = word.charAt(0);
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(c == board[i][j]){
dfs(i,j,"",word);
if(res == true)return true;
}
}
}
return false;
}
public void dfs(int row,int column,String cur,String word){
if(column == -1 || row == -1 || column == m || row == n)return;
if(visited[row][column]) return;
cur += board[row][column];
int length = cur.length();
if(word.charAt(length - 1) != cur.charAt(length - 1))return;
if(word.length() == cur.length()) {
res = true;
return;
}
visited[row][column] = true;
dfs(row,column + 1,cur,word);
dfs(row,column - 1,cur,word);
dfs(row + 1,column,cur,word);
dfs(row - 1,column,cur,word);
visited[row][column] = false;
}
}
这就是我第一次提交的代码 来看看执行效率
额… 看来这个方案还是有不少优化的余地的 让我们接着往下看
考虑中间过程
在学习动态规划的时候我们知道,很多题目其实只求最终的答案 这种时候我们不必存储中间过程
除非中间过程是一些你要多次求解的数据 此时缓存下来才是对提高时间效率有帮助的
在这里也是,我们没有必要把目前求得的整个字符串存储下来,我们只需要判断当前的相应位置字符是否匹配即可,所以引出优化一:省略中间过程
优化一:省略中间过程
//只判断当前位置就好啦 不符合就剪枝
if(word.charAt(curCount) != board[row][column])return;
class Solution {
char[][] board;
boolean res = false;
int n;
int m;
boolean[][] visited;
public boolean exist(char[][] board, String word) {
this.board = board;
n = board.length;
m = board[0].length;
visited = new boolean[n][m];
char c = word.charAt(0);
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(c == board[i][j]){
dfs(i,j,0,word);
if(res == true)return true;
}
}
}
return false;
}
public void dfs(int row,int column,int curCount,String word){
if(column == -1 || row == -1 || column == m || row == n)return;
if(visited[row][column]) return;
//只判断当前位置就好啦 不符合就剪枝
if(word.charAt(curCount) != board[row][column])return;
if(word.length() - 1 == curCount) {
res = true;
return;
}
visited[row][column] = true;
dfs(row,column + 1,curCount + 1,word);
dfs(row,column - 1,curCount + 1,word);
dfs(row + 1,column,curCount + 1,word);
dfs(row - 1,column,curCount + 1,word);
visited[row][column] = false;
}
}
时间和空间效率都大大地提升,主要原因是不需要存储大量的中间字符串,以及省去了对字符串进行操作的时间成本
考虑visited数组
visited数组是我定义的记录当前位置的布尔数组,这个能不能也想办法把它省去呢?这里对二维数组的存储和操作也是需要一些成本的
在没有提供原始数组的题目中(如求1…n的排列的所有组合),我们确实没办法,只能手动定义visited数组,但在本题可是有存储单词字母的二维数组的,我们把这个原始数组利用起来就可以省去visited数组的成本!所以引出优化二:省略visited数组
优化二:省略visited数组
这个时候题目的提示信息的重要性就体现出来了
既然原始数组仅由字母组成,那么我们用一个符号"."来表示这个位置已经访问过不能再次使用不就好了?
不过记得保存原始状态然后在dfs之后恢复
char c = board[row][column];
board[row][column] = '.';
//dfs....
board[row][column] = c;
class Solution {
char[][] board;
boolean res = false;
int n;
int m;
char[] arr;
public boolean exist(char[][] board, String word) {
this.board = board;
n = board.length;
m = board[0].length;
arr = word.toCharArray();
char c = word.charAt(0);
//找合适的起始点
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(c == board[i][j]){
dfs(i,j,0);
if(res == true)return true;
}
}
}
return false;
}
public void dfs(int row,int column,int cur){
//判断范围
if(column == -1 || row == -1 || column == m || row == n || cur == arr.length)return;
//判断是否已访问 是否匹配 剪枝
if(arr[cur] != board[row][column]) return;
if(cur == arr.length - 1) {
res = true;
return;
}
char c = board[row][column];
board[row][column] = '.';
dfs(row,column + 1,cur+1);
dfs(row,column - 1,cur+1);
dfs(row + 1,column,cur+1);
dfs(row - 1,column,cur+1);
board[row][column] = c;
}
}
这个优化同样可以用在如迷宫这种提供了原始数组的类似题目
到这里我们的dfs回溯剪枝优化就完成得差不多啦!
可以看到我的原始解法其实和最终优化后的解法的区别不大,但就是这些小小的细节可以大大提高我们算法的效率!