【面试-leetcode130】被围绕的区域(深度优先+广度优先)

147 阅读3分钟

freysteinn-g-jonsson-s94zCnADcUs-unsplash.jpg

这是leetcode面试刷题一题多解系列的第7篇,重点学习和理解下广度优先和深度优先算法。

题目

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

来源:力扣(LeetCode)
链接:leetcode.cn/problems/su…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

此题的核心在于:任何边界上的O都不会被填充为X,所以所有不被包围的O都直接或间接与边界上的O相连。所以我们遍历这个数组边界上的O,以他们为起点,标记所有与它直接或间接相连的字母O。最后我们遍历整个矩阵,对于每一个字母,如果被标记过,则将其还原为字母O,如果没有被标记过,我们将其修改为字母X。

题解1---深度优先

深度优先算法是一种用于遍历或搜索树或图的算法。其本质是通过深度优先遍历的方式,从起点开始一直沿着某一条路径搜索到底,直到到达终点或不能继续前进为止,然后回溯到上一个节点,选择另一条路径继续搜索,直到所有路径都被探索完毕。

function solve(board) {
  const m = board.length;
  const n = board[0].length;

  // 定义 DFS 函数
  const dfs = (i, j) => {
    if (i < 0 || j < 0 || i >= m || j >= n || board[i][j] !== 'O') {
      // 如果当前单元格超出边界或不是 'O',则停止 DFS
      return;
    }
    board[i][j] = '#'; // 将当前单元格标记为已访问
    dfs(i + 1, j); // 向下进行 DFS
    dfs(i - 1, j); // 向上进行 DFS
    dfs(i, j + 1); // 向右进行 DFS
    dfs(i, j - 1); // 向左进行 DFS
  };

  // 从所有边界单元格(左、右、上、下)开始 DFS
  for (let i = 0; i < m; i++) {
    dfs(i, 0); // 从左边界开始 DFS
    dfs(i, n - 1); // 从右边界开始 DFS
  }
  for (let j = 0; j < n; j++) {
    dfs(0, j); // 从上边界开始 DFS
    dfs(m - 1, j); // 从下边界开始 DFS
  }

  // 将所有剩余的 'O' 单元格替换为 'X',将 '#' 单元格恢复为 'O'
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (board[i][j] === 'O') {
        board[i][j] = 'X';
      } else if (board[i][j] === '#') {
        board[i][j] = 'O';
      }
    }
  }
}

在这个算法中,该算法的时间复杂度为 O(mn),其中 m 和 n 分别为矩阵的行数和列数。因为该算法遍历了整个矩阵,并且每个单元格最多被访问一次。 该算法的空间复杂度为 O(mn),主要是由 DFS 栈的空间复杂度引起的。在最坏情况下,如果矩阵中的所有单元格都是 'O',那么 DFS 栈的深度将达到 mn,因此空间复杂度为 O(mn)。

题解2---广度优先算法

广度优先算法是一种基于图论的搜索算法,用于遍历或搜索图形或树的数据结构。在广度优先搜索中,从根节点开始,按照从上到下、从左到右的顺序依次遍历所有节点,每次先访问当前节点的所有子节点,然后再访问子节点的子节点,直到遍历完整个图形或树。

var solve = function(board) {
  if (!board || board.length === 0) {
    return;
  }
  
  const m = board.length;
  const n = board[0].length;
  const queue = [];

  // 遍历边缘,将所有与边缘相连的 O 加入队列
  for (let i = 0; i < m; i++) {
    if (board[i][0] === 'O') {
      queue.push([i, 0]);
    }
    if (board[i][n - 1] === 'O') {
      queue.push([i, n - 1]);
    }
  }
  for (let j = 0; j < n; j++) {
    if (board[0][j] === 'O') {
      queue.push([0, j]);
    }
    if (board[m - 1][j] === 'O') {
      queue.push([m - 1, j]);
    }
  }

  // 将队列中所有的 O 及其相邻的 O 修改为 '#'
  while (queue.length > 0) {
    const [i, j] = queue.shift();
    if (i < 0 || i >= m || j < 0 || j >= n || 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]);
  }

  // 将矩阵中的 O 修改为 X,将 # 修改为 O
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (board[i][j] === 'O') {
        board[i][j] = 'X';
      }
      if (board[i][j] === '#') {
        board[i][j] = 'O';
      }
    }
  }
};

时间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。广度优先搜索过程中,每一个点至多只会被标记一次。 空间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。

广度优先搜索(BFS)和深度优先搜索(DFS)是两种最基本的图搜索算法。它们的本质区别在于搜索过程中遍历节点的顺序,深度优先搜索可以快速地搜索到图中深度较大的区域,但可能会陷入死循环。而广度优先搜索可以保证找到最短路径,但可能需要搜索整个图才能找到目标节点。选择哪种算法取决于具体的问题和图的结构。

我的更多前端资讯

欢迎大家技术交流 资料分享 摸鱼 求助皆可 —链接