最近做了一个剑指offer上的 剑指 Offer 12. 矩阵中的路径
这道题用了dfs和回溯算法,挺有典型意义的,在这里,列举一个,分析分析,加强记忆。
题目
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析
这道题就是要求先在数组中找到目标字符串的第一个字符及word[0],然后在这个元素的上下左右四个方向上中下一个字符,为了保证每个元素只能遍历一遍,可以将遍历过得元素变为true,但如果一条路真的无解了,就回溯到上一个元素上,这就需要一个变量来记录上一个元素。
分析题意, 这道题需要求目标字符串的路径, 且只要找到一个满足条件的即可, 所以特别适合用 DFS 来做. 因为 DFS 比较方便记录每条遍历过的路径, 正好可以用在这里
所以我们可以从任意一个字符开始, 依次比较它和目标字符串当前下标对应的字符, 如果相等的话, 就可以继续向四周遍历, 同时字符串下标加 1
直到字符串下标达到字符串长度, 这个时候就说明我们找到了一个满足要求的路径, 直接返回 true 即可。
.
而如果遍历过程中某个字符并不匹配, 我们就不要继续往下遍历下去了, 因为肯定不满足条件
外注意需要判断某个字符是否已经访问过, 所以我们需要额外定义一个 visit 集合, 用于存储已经访问的元素行列下标: 在递归调用某个字符之前将其加入行列下标加入集合中, 调用结束后还要将其移除, 恢复现场, 避免这个下标之后在其他路径中不能被用到
实现
经典的 DFS, 需要额外 visit 集合, 以及当前目标字符串的下标
依次遍历矩阵的每个字符, 如果当前字符与目标字符串的开头相同, 就以它为起点进行 DFS, 如果发现一条满足条件的路径就直接返回 true, 否则继续遍历
优化
visit 集合可以利用对原始矩阵的字符替换为 None 或其他不存在的字符代替, 而恢复时再将其替换回来即可, 这样可以节省一些空间
如果某个方向已经找到路径了, 就没必要继续遍历下去, 直接返回即可, 这样剪枝可以节省一些时间
代码
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
//dfs算法和回溯算法
for(let i = 0;i<board.length;i++){
for(let j = 0;j<board[0].length;j++){
if(board[i][j]===word[0]){
let isflag = dfs(board,word,i,j,0)
if(isflag)return true //isflag是true,就说明找到了
}
}
}
return false
};
//board是原数组,Word是目标数组,i是行,j是列,vis是目标数组的第几个数
function dfs(board,word,i,j,vis){
//中止条件
if(i<0||j<0 || i>=board.length || j>=board[0].length){
return false;
}
//获取Word目标的第VIS个字符,初始下标为0
let curChar = word.charAt(vis);
//如果字符不同则返回
if(curChar !== board[i][j]){
return false;
}
//如果单词最后一个字符匹配成功,则返回true
if(vis === word.length - 1){
return true;
}
//如果当前字符相同,则继续向下搜索
let oldChar = board[i][j];
//遍历过得数组设置为true
board[i][j] = true;
//四个方位遍历找到目标数
let flag = dfs(board,word,i+1,j,vis+1)||
dfs(board,word,i-1,j,vis+1)||
dfs(board,word,i,j+1,vis+1)||
dfs(board,word,i,j-1,vis+1);
// flag是false,代表这条路行不通,所以回溯到上一层的元素上去
board[i][j] = oldChar
return flag
}
代码的再优化
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
for(let i = 0;i<board.length;i++)
for(let j = 0;j<board[i].length;j++)
if(dfs(board,word,0,i,j))
return true
return false
function dfs(board,word,u,x,y){
if(board[x][y] !==word[u]) return false;
if(u === word.length-1) return true;
let dx = [-1,0,1,0],dy = [0,1,0,-1];
let t = board[x][y];
board[x][y] = '-';
for(let i = 0;i<4;i++){
let a = x+dx[i],b = y +dy[i];
if(a>=0&&a<board.length &&b>=0&&b<board[a].length){
if(dfs(board,word,u+1,a,b)) return true
}
}
board[x][y] = t;
return false
}
};