『力扣题解』多源BFS

294 阅读2分钟

多源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;
};