一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情
题目(Surrounded Regions)
链接:https://leetcode-cn.com/problems/surrounded-regions
解决数:1476
通过率:45.4%
标签:深度优先搜索 广度优先搜索 并查集 数组 矩阵
相关公司:google amazon uber
给你一个 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"]]
提示:
m == board.lengthn == board[i].length1 <= m, n <= 200board[i][j]为'X'或'O'
思路
-
必须是完全被围的O才能被换成X,也就是说边角上的O一定不会被围,进一步,与边角上的O相连的O也不会被X围四面,也不会被替换
-
把那些不需要被替换的O看成一个拥有独门绝技的门派,它们有一个共同祖师爷叫dummy,这些O和dummy互相连通,而那些需要被替换的O与dummy不连通
-
二维坐标(x,y)可以转换成x * n + y这个数(m是棋盘的行数,n是棋盘的列数)。二维坐标映射到一维的常用技巧
-
索引[0.. mn-1]都是棋盘内坐标的一维映射,让这个虚拟的dummy节点占据索引mn
-
适时增加虚拟节点,想办法让元素「分门别类」,建立动态连通关系。
-
具体的步骤如下
- 将首列和末列的 O 与 dummy 连通;将首行和末行的 O 与 dummy 连通
- 除首列、末列、首行、末行的 O 外,连通
- 判断所有不和 dummy 连通的 O,就能被替换了
代码
// 并查集
function UF(n) {
// n 为图的节点总数
// 记录连通分量
let num = n;
// 节点 x 的节点是 parent[x] // 父节点指针初始指向自己
let parent = new Array(n).fill(0).map((val, i) => i);
// 新增一个数组记录树的“重量”,最初每棵树只有一个节点,重量应该初始化 1
let size = new Array(n).fill(1);
// 返回某个节点 x 的根节点
const find = (x) => {
/* // 根节点的 parent[x] == x
while (parent[x] != x) {
x = parent[x];
} */
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
};
this.union = (p, q) => {
let rootP = find(p);
let rootQ = find(q);
if (rootP == rootQ) return;
/* // 将两棵树合并为一棵
parent[rootP] = rootQ;
// parent[rootQ] = rootP 或者写成这样也是ok的 */
// 小树接到大树下面,较平衡
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
// 两个分量合二为一
num--;
};
// 如果节点p和q连通的话,它们一定拥有相同的根节点
this.isConnected = (p, q) => {
let rootP = find(p);
let rootQ = find(q);
return rootP == rootQ;
};
this.count = () => {
return num;
};
}
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solve = function (board) {
if (!board.length) return;
let m = board.length,
n = board[0].length;
// 给 dummy 留一个额外位置
let uf = new UF(m * n + 1);
let dummy = m * n;
// 将首列和末列的 O 与 dummy 连通
for (let i = 0; i < m; i++) {
if (board[i][0] == "O") uf.union(i * n, dummy);
if (board[i][n - 1] == "O") uf.union(i * n + n - 1, dummy);
}
// 将首行和末行的 O 与 dummy 连通
for (let j = 0; j < n; j++) {
if (board[0][j] == "O") uf.union(j, dummy);
if (board[m - 1][j] == "O") uf.union(n * (m - 1) + j, dummy);
}
// 方向数组 d 是上下左右搜索的常用手法
let d = [
[1, 0],
[0, 1],
[0, -1],
[-1, 0],
];
for (let i = 1; i < m - 1; i++) {
for (let j = 1; j < n - 1; j++) {
if (board[i][j] == "O") {
// 将此 O 与上下左右的 O 连通
for (let k = 0; k < 4; k++) {
let x = i + d[k][0],
y = j + d[k][1];
if (board[x][y] == "O") {
uf.union(x * n + y, i * n + j);
}
}
}
}
}
// 所有不和 dummy 连通的 O,都要被替换
for (let i = 1; i < m - 1; i++) {
for (let j = 1; j < n - 1; j++) {
if (!uf.isConnected(dummy, i * n + j)) {
board[i][j] = "X";
}
}
}
};
DFS和BFS的解题思路
- 四边的利用DFS或BFS把O都变成特殊字符,比方说A
- 循环矩阵按照题目意思把单元格值为O都变成X,同时上面置为特殊字符A的还原为O
DFS实现代码
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solve = function (board) {
// 1四边的利用BFS把O都变成1
// 循环矩阵bfs把所有的O变成X
// bfs和dfs都行吧
let rows = board.length,
cols = board[0].length;
// 方向数组
let directions = [
[0, 1],
[0, -1],
[-1, 0],
[1, 0],
];
const dfs = (i, j) => {
if (
i < 0 ||
j < 0 ||
i >= rows ||
j >= cols ||
board[i][j] != "O" ||
board[i][j] == "A"
)
return;
board[i][j] = "A";
for (let [x, y] of directions) {
dfs(i + x, j + y);
}
};
for (let i = 0; i < cols; i++) {
// 第1行和最后一行
if (board[0][i] == "O") dfs(0, i);
if (board[rows - 1][i] == "O") dfs(rows - 1, i);
}
for (let i = 0; i < rows; i++) {
// 第1列和最后一列
if (board[i][0] == "O") dfs(i, 0);
if (board[i][cols - 1] == "O") dfs(i, cols - 1);
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (board[i][j] == "A") {
board[i][j] = "O";
} else if (board[i][j] == "O") {
board[i][j] = "X";
}
}
}
};
BFS实现代码
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solve = function (board) {
// 1四边的利用BFS把O都变成1
// 循环矩阵bfs把所有的O变成X
// bfs和dfs都行吧
let rows = board.length,
cols = board[0].length;
// 方向数组
let directions = [
[0, 1],
[0, -1],
[-1, 0],
[1, 0],
];
const bfs = (i, j) => {
let queue = [[i, j]];
while (queue.length) {
let size = queue.length;
while (size--) {
let [x, y] = queue.shift();
if (
x < 0 ||
y < 0 ||
x >= rows ||
y >= cols ||
board[x][y] != "O" ||
board[x][y] == "A"
)
continue;
board[x][y] = "A";
for (let [curI, curJ] of directions) {
queue.push([curI + x, curJ + y]);
}
}
}
};
for (let i = 0; i < cols; i++) {
// 第1行和最后一行
if (board[0][i] == "O") bfs(0, i);
if (board[rows - 1][i] == "O") bfs(rows - 1, i);
}
for (let i = 0; i < rows; i++) {
// 第1列和最后一列
if (board[i][0] == "O") bfs(i, 0);
if (board[i][cols - 1] == "O") bfs(i, cols - 1);
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (board[i][j] == "A") {
board[i][j] = "O";
} else if (board[i][j] == "O") {
board[i][j] = "X";
}
}
}
};