【每日算法0224】搜索与回溯算法(中等)

82 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情

题目

剑指 Offer 12. 矩阵中的路径

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

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

image-20230222153243469

示例:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

分析

该题属于基础不好答案都要看半天的类型>_<

这里说下自己第一次做出来时的解题思路吧

  • 先把题目简化,把复杂的逻辑判断先放一边,简单列举出边界,遍历整个矩阵,于是可得
function exist(board: string[][], word: string): boolean {
    let wMax = board[0].length
    let hMax = board.length
​
    for (let i = 0; i < hMax; i ++) {
        for (let j = 0; j < wMax; j ++) {
            // 判断逻辑。。。
        }
    }
};
  • 进一步分析题目要求,按顺序连接字母单词,即可转化为问题:

    判断矩阵上每个出发是否满足要求,那我们在写一个方法,对每个点进行判断,只要找到满足要求的点,反回既是所求

function exist(board: string[][], word: string): boolean {
    let wMax = board[0].length
    let hMax = board.length
    
    // 判断符合条件的点
    function check (i, j) {
        // ...
    }
​
    for (let i = 0; i < hMax; i ++) {
        for (let j = 0; j < wMax; j ++) {
            // 判断逻辑。。。
            if (check(i, j)) {
                return true
            }
        }
    }
};

现在问题的大头就是 check 函数逻辑如何写了

  • 确定需要传入的参数,位置索引必传,用于获取格子上的字母,还有就是当前的字母索引,用于两个值对比
    function check(i, j, index) {
        let cur = board[i][j]
        let key = word[index]
​
        let result = false
​
        if (cur == key) {
            result = true
            return result
        }
​
        return result
    }

这个时候一个迭代函数的雏形就出来了,我们按照迭代的方法完善这个函数

  • 确定终止条件

如果 index 大于字母长度,则说明迭代完存在满足条件的字符链

如果 i 或 j 超过边界,则说明该点不满足要求

if (index == word.length) {
    return true
}
if (i >= hMax || j >= wMax || i < 0 || j < 0) {
    return false
}

如果该点已经访问过,那么该点也不满足

这里有个麻烦的点就是如何判断某个点是否已经访问过

在答案的启发下,我构造了一个 board 同大小的数组,用来存放每个点的访问情况

    let visited = new Array(hMax)
    for (let n = 0 ; n < hMax; n ++) {
        visited[n] = new Array(wMax).fill(false)
    }

每进入一个点,将该点值设为 true,如果迭代结果为 false,需要重新把该点改为 false

  • 确定返回值

如果当前坐标满足要求,需要递归判断下一个点(上下左右)是否满足要求,直到退出

那么有

result = check(i + 1, j, index) || 
    check(i - 1, j, index) || 
    check(i, j + 1, index) ||
    check(i, j - 1, index)

最终完善递归逻辑

        let cur = board[i][j]
        let key = word[index]
​
        let result = false
​
        if (cur == key) {
            index ++
            visited[i][j] = true
            result = check(i + 1, j, index) || 
                check(i - 1, j, index) || 
                check(i, j + 1, index) ||
                check(i, j - 1, index)
        }
​
        if (result) {
            return true
        } else {
            visited[i][j] = false
            return false
        }

实现

function exist(board: string[][], word: string): boolean {
    let wMax = board[0].length
    let hMax = board.length
​
    let visited = new Array(hMax)
    for (let n = 0 ; n < hMax; n ++) {
        visited[n] = new Array(wMax).fill(false)
    }
​
    // check 判断某一个点能否从头到尾连接单词
    function check(i, j, index) {
        if (index == word.length) {
            return true
        }
        if (i >= hMax || j >= wMax || i < 0 || j < 0) {
            return false
        }
        if (visited[i][j]) {
            return false
        }
        
        let cur = board[i][j]
        let key = word[index]
​
        let result = false
​
        if (cur == key) {
            index ++
            visited[i][j] = true
            result = check(i + 1, j, index) || 
                check(i - 1, j, index) || 
                check(i, j + 1, index) ||
                check(i, j - 1, index)
        }
​
        if (result) {
            return true
        } else {
            visited[i][j] = false
            return false
        }
    }
​
    for (let i = 0; i < hMax; i ++) {
        for (let j = 0; j < wMax; j ++) {
            if (check(i, j, 0)) {
                return true
            }
        }
    }
​
    return false
};