剑指 Offer 12. 矩阵中的路径

198 阅读3分钟

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。

img

题解

这题是关于深度优先搜索算法的一道递归题。

每次一碰到递归的题目就很头疼,再加上还没有系统的学习图的dfs算法。虽然 开始的整体思路是对的,但是却写不出来代码。可能是相关的代码框架记得不熟练的关系。

/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function(board, word) {
    var cur = 0;
    var m = board.length;
    var n = board[0].length;
    function dfs(i,j,k){
        //i,j是现在的数的坐标
        if(i<0||j<0||i>=m||j>=n||board[i][j] !== word[k]){
            return false;
        }
        //到这里说明board[i][j] == word[k];
        if(k === word.length - 1) return true;//最后一个字母,可以结束递归了
        var tmp = board[i][j];  // 记录到board的值
        board[i][j] = '';   // 锁上,因为后续的递归是4个方向上的,无法保证上一个方向的值
        // up:i-1,j
        // down:i+1,j
        // left:i,j-1
        // right:i,j+1
        var isTrue =  dfs(i-1,j,k+1) || dfs(i+1,j,k+1) || dfs(i,j-1,k+1) || dfs(i,j+1,k+1);
        board[i][j] = tmp;// 恢复现场
        return isTrue;
    }
    for(let i=0;i<m;i++){
        for(let j=0;j<n;j++){
            if(dfs(i,j,cur))return true;
        }
    }
    return false;
};

其中board[i][j] !== word[k]这个if条件判断做的很好,有效的筛除了更多的情况,(我自己是没想到)。那个上锁的地方,恢复现场是因为,若果当前这个位置上,无法找出一条正确的路径,就要重新遍历其他的点,但是在遍历其他的点时,刚才的那个点遍历不成功的点可能会成为现在这个遍历的路径上的一个必须的点,如果不恢复,就无法便利成功。

比如:

[["C","A","A"],
 ["A","A","A"],
 ["B","C","D"]]
 现在要找:“AAB”

当我们第一次遍历到:(1,0)位置的A时,我们此时以为这个是AAB的起点,于是将其置空,在进行完后面的上下左右判断后,我们发现,这个点好像不是很行,但是我们没有把它恢复。

这就导致了,当我们遍历到(1,1)位置的A时,在以(1,1)位置的A为起点去寻找路径,但是由于刚才的(1,0)的A已经为“ ”空,我们就会误判这条路是行不通的,但是事实上是可行的,如下:

[["C","A","A"], ["A","A","A"], ["B","C","D"]]

只是必须要经过(1,0)上的A。

所以这题其实,真正难的不是思路,毕竟我觉得dfs那个上下左右判断和二重for循环其实很暴力了,真正难的是边界条件的判定,和整个代码结构的设计。