这是我参与更文挑战的第 25 天,活动详情查看 更文挑战
这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
136. 被围绕的区域 (surrounded-regions)
标签
- DFS
- BFS
- 中等
题目
这里不贴题了,leetcode打开就行,题目大意:
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例 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
就是内部封闭的位置。
所以我们的步骤就是:
- 把最外圈的每个
O
位置作为'起点'查找O
的联通路径,打上标记K
- 全部标记完之后,中间封闭的
O
应该都转为X
, 其余K
转成O
查找O
的联通路径可以有两种方式
- DFS(深度优先遍历)
- 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
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧