前端算法必刷题系列[72]

210 阅读3分钟

这是我参与更文挑战的第 25 天,活动详情查看 更文挑战

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

136. 被围绕的区域 (surrounded-regions)

标签

  • DFS
  • BFS
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

image.png

示例 1

输入:board = [["X","X","X","X"],
["X","O","O","X"],
["X","X","O","X"],
["X","O","X","X"]]
输出:[["X","X","X","X"],
["X","X","X","X"],
["X","X","X","X"],
["X","O","X","X"]]

解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 
任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。
如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2

输入:board = [["X"]]
输出:[["X"]]

基本思路

这个问题的本质是,如果最边上有 O, 中间元素没有通路到边上O的话,则就是封闭的位置,把这些封闭位置变成 X

那么我们可以把最外圈的每个 O 位置作为'起点',看它能联通多少 O位置。联通的位置打上标记,其余的 O就是内部封闭的位置。

所以我们的步骤就是:

  1. 把最外圈的每个 O 位置作为'起点'查找O的联通路径,打上标记 K
  2. 全部标记完之后,中间封闭的 O 应该都转为 X, 其余K转成 O

查找O的联通路径可以有两种方式

  1. DFS(深度优先遍历)
  2. BFS(广度优先遍历)

对这两个概念,之前有详细写。强烈建议回顾下这篇二叉树的层序遍历, 是非常典型使用场景。

写法实现

DFS实现

var solve = function(board) {
    // n 行 m 列
    let [n, m] = [board.length, board[0].length]

    // 把最外圈的每个 `O` 位置作为'起点'查找`O`的联通路径,打上标记 K
    // 也就是说 K 标记的位置是联通边界的非封闭位置
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < m; j++) {
            // 表示在最外层边界上
            if (i === 0 || j === 0 || i === n - 1 || j === m - 1) {
                // 以 O 为起点深度搜索联通的 O路径
                if (board[i][j] === 'O') {
                    dfs(i, j)
                }
            }
        }
    }

    // 全部标记完之后,中间的 O 应该都转为 X, 其余K转成 O
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < m; j++) {
            // 先把封闭的 O 全改 X
            if (board[i][j] === 'O') {
                board[i][j] = 'X'
            } else if (board[i][j] === 'K') {
                // 再把 K位置 改回 O
                board[i][j] = 'O'
            }
        }
    }

    function dfs(i, j) {
        // 越界就直接return
        if (i < 0 ||  j < 0 || i === n || j === m) {
            return
        }
        // 如果遇到 O 位置,标记 K
        if (board[i][j] === 'O') {
            // 再向4个方向上搜索
            board[i][j] = 'K'
            dfs(i+1, j)
            dfs(i-1, j)
            dfs(i, j+1)
            dfs(i, j-1)
        }
    }

    return board
};

let board = [
["X","X","X","X"],
["X","O","O","X"],
["X","X","O","X"],
["X","O","X","X"]]

console.log(solve(board))
  • 时间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。深度优先搜索过程中,每一个点至多只会被标记一次。

  • 空间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。主要为深度优先搜索的栈的开销

BFS实现

var solve = function(board) {
    // n 行 m 列
    let [n, m] = [board.length, board[0].length]
    const directionDict = [[1, 0], [-1, 0], [0, 1], [0, -1]]

    // 把最外圈的每个 `O` 位置作为'起点'查找`O`的联通路径,打上标记 K
    // 也就是说 K 标记的位置是联通边界的非封闭位置
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < m; j++) {
            // 表示在最外层边界上
            if (i === 0 || j === 0 || i === n - 1 || j === m - 1) {
                // 以 O 为起点深度搜索联通的 O路径
                if (board[i][j] === 'O') {
                    bfs(i, j)
                }
            }
        }
    }

    // 全部标记完之后,中间的 O 应该都转为 X, 其余K转成 O
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < m; j++) {
            // 先把封闭的 O 全改 X
            if (board[i][j] === 'O') {
                board[i][j] = 'X'
            } else if (board[i][j] === 'K') {
                // 再把 K位置 改回 O
                board[i][j] = 'O'
            }
        }
    }

    // 跟上面不同的搜索方式
    function bfs(i, j) {
        // 用一个队列来存放当前层节点
        let queue = [[i, j]]
        // 队列中的都是边路联通的,做标记 K
        board[i][j] = 'K'
        while (queue.length) {
            // 从队列中取出元素
            let tempItem = queue.pop()
            for (const [dx, dy] of directionDict) { 
                // 算出相邻的4个位置的坐标
                let [x, y] = [tempItem[0] + dx, tempItem[1] + dy]
                // 越界排除
                if (x < 0 || x === n || y < 0 || y === m) {
                    continue;
                }
                if (board[x][y] == 'O') {
                    board[x][y] = 'K';
                    // 并推入队列作为下一个层的种子
                    queue.push([x, y])
                }
            }    
        }
    }

    return board
};

let board = [
["X","X","X","X"],
["X","O","O","X"],
["X","X","O","X"],
["X","O","X","X"]]

console.log(solve(board))
  • 时间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。广度优先搜索过程中,每一个点至多只会被标记一次。

  • 空间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。主要为广度优先搜索的队列的开销

另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考