算法题解——保姆级注释
一、前言
矩阵中的路径是应用深度优先遍历算法的一道经典题目,在力扣中的难度等级是中等,这里先简单介绍一下平时经常接触到的深搜算法,这种算法在树和图的数据结构中有着非常普遍的应用。
深度优先搜索(Depth-First-Search,DFS)
用最直观的树来展示,在上图中,目标是遍历访问所有节点,如果是广度优先搜索,会逐层优先从左往右进行遍历,遍历顺序为ABCDEFG,而如果是深度优先,则会是优先从上往下进行搜索,则遍历顺序为ABDECFG
当然,在实际的场景下,不一定是这样的遍历顺序,但是应用的是相同的遍历思想。
二、题目
那我们现在来看看题目
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
示例 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
board 和 word仅由大小写英文字母组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof
简单来说,题目给你一个字母矩阵和一个单词,看你能不能用笔把这个字母在图中连续的涂出来(是否存在),这当然需要我们遍历搜索可能的路径,并在过程中对各种情况进行判断了。
三、题解
直接上代码
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
if(board.length*board[0].length<word.length) return false; //若矩阵元素小于单词长度,必不存在
let x=0; //单词的当前长度
board = board.map((e)=>{ //这里对矩阵进行预处理,每个元素添加一个标记判断是否经过
return e.map(d=>[d,false]);
})
for(let i = 0 ;i<board.length;i++){ //遍历矩阵,若元素和第一个字母相等则判断能否找到所有字母
for(let j = 0 ;j<board[0].length;j++){
if(board[i][j][0]==word[x]&&searchNode(i,j,x,word,board)) return true;
}
}
return false
};
var searchNode = function(i,j,x,word,board){
if(board[i][j][1]==false&&board[i][j][0]==word[x]){ //若元素未被经过,继续
if(x==word.length-1) return true; //递归出口,若来到最后一位,则矩阵中存在该字母
board[i][j][1] = true; //经过的元素将标记取反
if(board[i][j-1]&&searchNode(i,j-1,x+1,word,board)){ //如果该方向下个字母存在,递归
return true; //该路径存在完整单词
}
if(board[i][j+1]&&searchNode(i,j+1,x+1,word,board)){
return true;
}
if(board[i+1]&&searchNode(i+1,j,x+1,word,board)){
return true;
}
if(board[i-1]&&searchNode(i-1,j,x+1,word,board)){
return true;
}
board[i][j][1] = false; //四个方向都没有结果,此路不通,逐步往前释放标记,回溯查找其他路径
}
return false; //此路不通,返回false
}
看看思路
- 首先确定基本思路,遍历每个节点,若该节点值等于单词首字母,则递归进行深搜判断此路是否符合要求
- 我们需要给每个元素做个标记来判断是否被搜索过,所以先对数组进行预处理,把每个元素变成二维数组,加上一个布尔值作为标记。
- 然后遍历数组,若该节点值等于单词首字母,就传入深搜函数递归判断。
- 在递归中要确定判断条件,如果该元素没有被搜索过,并且等于单词的当前字母,则可以继续搜索,否则该此路不通(返回false)。
- 然后给递归一个出口,如果已经到了单词的最后一个字母,证明这条路径符合要求,返回true结束搜索。
- 在对四个方向搜索前,需要先给现在的位置做上标记“到此一游”,将前面的布尔值取反,之后遍历到此处即可放弃该路径。
- 对四个方向进行搜索,通过该元素是否存在来判断是否到达边界。如果该方向的递归传来了true的信号,我们就返回消息true,传上去!这条路可以走!
- 如果四个方向都没有路(元素不符合要求),就在后撤过程中把标记清除,避免妨碍其他路径的搜索。然后传回消息,此路不通(返回false)!
注意事项
(我踩的一些很低级的坑)
1、可以加入一些判断条件进行优化,使搜索的途径减少,也就是剪枝,代码中对矩阵元素个数与字母个数先进行一次比较,就算是一个小优化。
2、用递归做判断条件时,请返回true,不要返回false。
3、不要用 i++,++i 等作为形参传入函数,直接使用 i+1即可,否则可能会改变外层作用域中 i 的值,影响到其他路径的判断。
四、总结
深搜常常需要用到递归,所以要先对递归有着熟练的掌握,然后思考各种边界值的判定,当然,类似的深搜的题目都有固定的套路,最好先找出其中的固定规律,之后套模板就轻松多了。这道题遍历每个元素,对每个可能的元素向下挖掘所有的可能性,直到搜索完毕再进行下一个元素的搜索,这就是前面介绍的深搜思想的应用。
希望本篇题解能对大家有所帮助