多源BFS
今天看到叶老师的博客【图论搜索专题】如何使用「多源 BFS」降低时间复杂度,第一次接触到多源BFS,刚好第357次周赛的第三题也用到,就来学习一下。
多源和单源BFS在实现上无太大区别,只是对于单源,队列中一开始只放置一个结点,而多源需要把这些源都放入队列。在逻辑上,可以把这多个源看作一个整体,即“虚拟源点”。
1162. 地图分析
这题是多源BFS的模板题,所有海洋点构成虚拟源点,求的是所有陆地点中,离虚拟源点最远的点。在实现上,就是先把所有海洋点入队,后面和普通BFS并无区别。
/**
* @param {number[][]} grid
* @return {number}
*/
var maxDistance = function (grid) {
let ans = -1;
const n = grid.length;
const queue = [];
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1]
];
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] === 1) {
queue.push([i, j, 0]);
}
}
}
// 全是海洋点
if (queue.length === n * n) {
return -1;
}
// 如果全是陆地点,队列为空,不会进while循环,也返回-1
while (queue.length > 0) {
const node = queue.shift();
const [x, y, k] = node;
ans = Math.max(ans, k);
for (const [dx, dy] of dirs) {
if (dx + x < 0 || dx + x >= n || dy + y < 0 || dy + y >= n) {
continue;
}
if (grid[x + dx][y + dy] === 0) {
// 这里grid也起到了book的功效,让每个点只被处理一次
grid[x + dx][y + dy] = 1;
queue.push([x + dx, y + dy, k + 1]);
}
}
}
return ans;
};
两层for循环的时间复杂度是O(n ^ 2),而while循环会把所有结点遍历一次,也是O(n ^ 2),所以总的时间复杂度是O(n ^ 2)。
1020. 飞地的数量
这题用1表示陆地,0表示海洋,求的是不与边缘陆地相连的陆地的数量。可以先把边缘所有的陆地点加入队列(并且设置为海洋,该位置0),然后进行BFS,过程中如果遇到了陆地,就把陆地变成海洋。最后遍历整个图,计算此时陆地的数。
把陆地置为海洋,不仅便于最后的统计,还可以避免某一陆地被重复处理,第一次处理该陆地时已经把它变成海洋了,第二次就不再处理它。
/**
* @param {number[][]} grid
* @return {number}
*/
var numEnclaves = function (grid) {
const m = grid.length;
const n = grid[0].length;
const queue = [];
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1]
];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
if (grid[i][j] === 1) {
queue.push([i, j]);
grid[i][j] = 0;
}
}
}
}
while (queue.length > 0) {
const node = queue.shift();
const [x, y] = node;
for (const [dx, dy] of dirs) {
if (dx + x < 0 || dx + x >= m || dy + y < 0 || dy + y >= n) {
continue;
}
if (grid[x + dx][y + dy] === 1) {
queue.push([x + dx, y + dy]);
grid[x + dx][y + dy] = 0;
}
}
}
let ans = 0;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] === 1) {
ans++;
}
}
}
return ans;
};
130. 被围绕的区域
这题和上一题很类似,和边缘的O相连的O保持不变,其余O会改为X。所以一开始把边缘的O都加入队列,然后BFS,BFS遍历到的O都是不用修改的,因此在book中标记。
最后根据book,把某些O设置成X。
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solve = function (board) {
const m = board.length;
const n = board[0].length;
const queue = [];
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1]
];
const book = Array(m)
.fill(false)
.map(() => Array(n).fill(false));
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
if (board[i][j] === 'O') {
queue.push([i, j]);
// 为true表示不用修改为'X'
book[i][j] = true;
}
}
}
}
while (queue.length > 0) {
const node = queue.shift();
const [x, y] = node;
for (const [dx, dy] of dirs) {
if (dx + x < 0 || dx + x >= m || dy + y < 0 || dy + y >= n) {
continue;
}
const i = x + dx;
const j = y + dy;
if (!book[i][j]) {
if (board[i][j] === 'O') {
queue.push([i, j]);
book[i][j] = true;
}
}
}
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (board[i][j] === 'O' && !book[i][j]) {
board[i][j] = 'X';
}
}
}
return board;
};