返回

JS 通关 LeetCode 130:围困区域

前端

背景

在 LeetCode 130 中,我们面临一道具有挑战性的难题,即确定并填充被“X”包围的“O”区域。这需要敏锐的算法思维和对 JavaScript 语言的熟练运用。

突破障碍:三种高效算法

为了征服这道难题,我们将探索三种独特的算法:

1. 深度优先搜索(DFS):递归与 DFS 队列

思路:

递归 DFS 遍历矩阵,标记“O”区域为“U”,表示未被包围。如果找到与边界相连的“O”,则将其标记为“#”,表示不会被填充。

代码:

const solveDFS = (board) => {
  if (!board || board.length === 0) return;
  const rows = board.length, cols = board[0].length;

  // 标记边界上的 O
  for (let i = 0; i < rows; i++) {
    markUnbound(board, i, 0);
    markUnbound(board, i, cols - 1);
  }
  for (let j = 0; j < cols; j++) {
    markUnbound(board, 0, j);
    markUnbound(board, rows - 1, j);
  }

  // DFS 遍历
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] === 'O') fillSurrounded(board, i, j);
    }
  }

  // 还原标记
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] === 'U') board[i][j] = 'O';
      else if (board[i][j] === '#') board[i][j] = 'O';
    }
  }
};

const markUnbound = (board, i, j) => {
  if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] !== 'O') return;
  board[i][j] = '#';
};

const fillSurrounded = (board, i, j) => {
  if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] !== 'O') return;
  board[i][j] = 'U';
  fillSurrounded(board, i - 1, j); // 上
  fillSurrounded(board, i + 1, j); // 下
  fillSurrounded(board, i, j - 1); // 左
  fillSurrounded(board, i, j + 1); // 右
};

2. DFS:使用 DFS 队列

思路:

使用队列存储与边界相连的“O”区域,从这些区域出发进行 DFS,标记所有未被包围的“O”为“#”。

代码:

const solveDFSQueue = (board) => {
  if (!board || board.length === 0) return;
  const rows = board.length, cols = board[0].length;

  const queue = [];

  // 入队边界上的 O
  for (let i = 0; i < rows; i++) {
    if (board[i][0] === 'O') queue.push([i, 0]);
    if (board[i][cols - 1] === 'O') queue.push([i, cols - 1]);
  }
  for (let j = 0; j < cols; j++) {
    if (board[0][j] === 'O') queue.push([0, j]);
    if (board[rows - 1][j] === 'O') queue.push([rows - 1, j]);
  }

  // DFS
  while (queue.length) {
    const [i, j] = queue.shift();
    if (i < 0 || i >= rows || j < 0 || j >= cols || board[i][j] !== 'O') continue;
    board[i][j] = '#';
    queue.push([i - 1, j]); // 上
    queue.push([i + 1, j]); // 下
    queue.push([i, j - 1]); // 左
    queue.push([i, j + 1]); // 右
  }

  // 还原标记
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] === 'U') board[i][j] = 'O';
      else if (board[i][j] === '#') board[i][j] = 'O';
    }
  }
};

3. 广度优先搜索(BFS):使用队列

思路:

使用队列存储与边界相连的“O”区域,从这些区域出发进行 BFS,将未被包围的“O”标记为“#”。

代码:

const solveBFS = (board) => {
  if (!board || board.length === 0) return;
  const rows = board.length, cols = board[0].length;

  const queue = [];

  // 入队边界上的 O
  for (let i = 0; i < rows; i++) {
    if (board[i][0] === 'O') queue.push([i, 0]);
    if (board[i][cols - 1] === 'O') queue.push([i, cols - 1]);
  }
  for (let j = 0; j < cols; j++) {
    if (board[0][j] === 'O') queue.push([0, j]);
    if (board[rows - 1][j] === 'O') queue.push([rows - 1, j]);
  }

  // BFS
  while (queue.length) {
    const [i, j] = queue.shift();
    if (i < 0 || i >= rows || j < 0 || j >= cols || board[i][j] !== 'O') continue;
    board[i][j] = '#';
    queue.push([i - 1, j]); // 上
    queue.push([i + 1, j]); // 下
    queue.push([i, j - 1]); // 左
    queue.push([i, j + 1]); // 右
  }

  // 还原标记
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] === 'U') board[i][j] = 'O';
      else if (board[i][j] === '#') board[i][j] = 'O';
    }
  }
};

选择最优算法

对于这道难题,三种算法的效率非常接近。以下是一些选择建议:

  • 推荐:DFS 队列 ,代码简练、执行效率高。
  • 替代方案:DFS 递归 ,递归方式更直观,但可能存在函数调用堆叠限制。
  • 备选方案:BFS 队列 ,队列操作更简单,但速度略慢。