记录 1 道算法题
被围绕的区域
要求:提供一个 m * n 的矩阵,里面填充字符 X 或 O,将被 X 围绕的区域中的 O 填充为 X。在边上的 O 跟与之相连的 O 不视为被围绕。
比如:
[ ["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"]
]
- dfs / bfs
可以沿着 4 个边通过 bfs 或者 dfs 的方式不重复的进行探索,将探索到的 O 标记为 A,这时候不被围绕的就是 A,剩下的 O 则是被围绕,然后遍历全部,将 A 变成 O,将 O 变成 X 即可。
function solve(board) {
const row = board.length
const col = board[0].length
const queue = []
// 从左右两个边开始收集
for (let i = 0; i < row; i++) {
if (board[i][0] === 'O') {
queue.push([i, 0])
board[i][0] = 'A'
}
if (board[i][col - 1] === 'O') {
queue.push([i, col - 1])
board[i][col - 1] = 'A'
}
}
// 从上下两个边开始收集 四个角不重复
for (let i = 1; i < col - 1; i++) {
if (board[0][i] === 'O') {
queue.push([0, i])
board[0][i] = 'A'
}
if (board[row - 1][i] === 'O') {
queue.push([row - 1, i])
board[row - 1][i] = 'A'
}
}
const transfer = (x, y) => {
// 在允许的范围内寻找 O
if (x < 0 || x >= col || y < 0 || y >= row || board[x][y] !== 'O') {
return
}
board[x][y] = 'A'
queue.push([x,y])
}
// 深度遍历构建关系
while(queue.length) {
const [x, y] = queue.pop()
transfer(x - 1, y)
transfer(x + 1, y)
transfer(x, y - 1)
transfer(x, y + 1)
}
// 将 A 转化为 O,将 O 转换为 X
for(let i = 0; i < row; i++) {
for(let j = 0; j < col; j++) {
if (board[i][j] === 'A') {
board[i][j] = 'O'
} else if (board[i][j] === 'O') {
board[i][j] = 'X'
}
}
}
return board
}
- 并查集
收集关系可以使用并查集,如果是与 4 条边上的字符处于同一个集合,那么意味着不是被围绕的区域。
下面的代码只是一个思路,有不少可以优化的地方。
function solve(board) {
const row = board.length
const col = board[0].length
const union = Array.from(Array(row * col), (_, i) => i)
// 二维坐标转一维坐标
function computeIndex(x, y, size) {
return x * size + y
}
// 将 4 条边都收集到某一个点的集合下,这里收集到左上角 0。
for (let i = 0; i < row; i++) {
if (board[i][0] === 'O') {
union[computeIndex(i, 0, row)] = 0
}
if (board[i][col - 1] === 'O') {
union[computeIndex(i, col - 1, row)] = 0
}
}
for (let i = 1; i < col - 1; i++) {
if (board[0][i] === 'O') {
union[computeIndex(0, i, row)] = 0
}
if (board[row - 1][i] === 'O') {
union[computeIndex(row - 1, i, row)] = 0
}
}
// 并查集的查找和压缩
const find = (i) {
while(union[i] !== i) {
union[i] = union[union[i]]
i = union[i]
}
return i
}
// 确认并建立两个集合的联系
const connect = (a, b) => {
const n1 = find(a)
const n2 = find(b)
if (n1 !== n2) {
// 将大的数归到小的数的集合里
n1 < n2 ? (union[n2] = n1) : (union[n1] = n2)
}
}
// 添加集合
for (let i = 1; i < row - 1; i++) {
for (let j = 1; j < col - 1; j++) {
if (board[i][j] === 'O') {
// 4 个方向上查找,存在重复的情况,需要优化
const index = computeIndex(i, j, row)
board[i + 1][j] === 'O' &&
connect(index, computeIndex(i + 1, j, row))
board[i - 1][j] === 'O' &&
connect(index, computeIndex(i - 1, j, row))
board[i][j + 1] === 'O' &&
connect(index, computeIndex(i, j + 1, row))
board[i][j - 1] === 'O' &&
connect(index, computeIndex(i, j - 1, row))
}
}
}
// 将跟左上角 0 同一个集合的字符都转成 X
for (let i = 1; i < row - 1; i++) {
for (let j = 1; j < col - 1; j++) {
const index = computeIndex(i, j, row)
if (index === find(0) && board[i][j] === 'O') {
board[i][j] = 'X'
}
}
}
return board
}