【前端刷题】130.被围绕的区域(MEDIUM)

141 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

题目(Surrounded Regions)

链接:https://leetcode-cn.com/problems/surrounded-regions
解决数:1476
通过率:45.4%
标签:深度优先搜索 广度优先搜索 并查集 数组 矩阵 
相关公司:google amazon uber 

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

 

示例 1:

输入: board = [["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"]]
解释: 被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2:

输入: board = [["X"]]
输出: [["X"]]

 

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] 为 'X' 或 'O'

思路

  • 必须是完全被围的O才能被换成X,也就是说边角上的O一定不会被围,进一步,与边角上的O相连的O也不会被X围四面,也不会被替换

  • 把那些不需要被替换的O看成一个拥有独门绝技的门派,它们有一个共同祖师爷叫dummy,这些O和dummy互相连通,而那些需要被替换的O与dummy不连通

  • 二维坐标(x,y)可以转换成x * n + y这个数(m是棋盘的行数,n是棋盘的列数)。二维坐标映射到一维的常用技巧

  • 索引[0.. mn-1]都是棋盘内坐标的一维映射,让这个虚拟的dummy节点占据索引mn

  • 适时增加虚拟节点,想办法让元素「分门别类」,建立动态连通关系。

  • 具体的步骤如下

  1. 将首列和末列的 O 与 dummy 连通;将首行和末行的 O 与 dummy 连通
  2. 除首列、末列、首行、末行的 O 外,连通
  3. 判断所有不和 dummy 连通的 O,就能被替换了

代码

// 并查集
function UF(n) {
  // n 为图的节点总数
  // 记录连通分量
  let num = n;
  //  节点 x 的节点是 parent[x] // 父节点指针初始指向自己
  let parent = new Array(n).fill(0).map((val, i) => i);
  // 新增一个数组记录树的“重量”,最初每棵树只有一个节点,重量应该初始化 1
  let size = new Array(n).fill(1);
  // 返回某个节点 x 的根节点
  const find = (x) => {
    /* // 根节点的 parent[x] == x
    while (parent[x] != x) {
      x = parent[x];
    } */
    while (parent[x] != x) {
      // 进行路径压缩
      parent[x] = parent[parent[x]];
      x = parent[x];
    }
    return x;
  };
  this.union = (p, q) => {
    let rootP = find(p);
    let rootQ = find(q);
    if (rootP == rootQ) return;
    /* // 将两棵树合并为一棵
    parent[rootP] = rootQ;
    // parent[rootQ] = rootP 或者写成这样也是ok的 */
    // 小树接到大树下面,较平衡
    if (size[rootP] > size[rootQ]) {
      parent[rootQ] = rootP;
      size[rootP] += size[rootQ];
    } else {
      parent[rootP] = rootQ;
      size[rootQ] += size[rootP];
    }
    // 两个分量合二为一
    num--;
  };
  // 如果节点p和q连通的话,它们一定拥有相同的根节点
  this.isConnected = (p, q) => {
    let rootP = find(p);
    let rootQ = find(q);
    return rootP == rootQ;
  };
  this.count = () => {
    return num;
  };
}

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solve = function (board) {
  if (!board.length) return;
  let m = board.length,
    n = board[0].length;
  // 给 dummy 留一个额外位置
  let uf = new UF(m * n + 1);
  let dummy = m * n;
  // 将首列和末列的 O 与 dummy 连通
  for (let i = 0; i < m; i++) {
    if (board[i][0] == "O") uf.union(i * n, dummy);
    if (board[i][n - 1] == "O") uf.union(i * n + n - 1, dummy);
  }
  // 将首行和末行的 O 与 dummy 连通
  for (let j = 0; j < n; j++) {
    if (board[0][j] == "O") uf.union(j, dummy);
    if (board[m - 1][j] == "O") uf.union(n * (m - 1) + j, dummy);
  }
  // 方向数组 d 是上下左右搜索的常用手法
  let d = [
    [1, 0],
    [0, 1],
    [0, -1],
    [-1, 0],
  ];
  for (let i = 1; i < m - 1; i++) {
    for (let j = 1; j < n - 1; j++) {
      if (board[i][j] == "O") {
        // 将此 O 与上下左右的 O 连通
        for (let k = 0; k < 4; k++) {
          let x = i + d[k][0],
            y = j + d[k][1];
          if (board[x][y] == "O") {
            uf.union(x * n + y, i * n + j);
          }
        }
      }
    }
  }
  // 所有不和 dummy 连通的 O,都要被替换
  for (let i = 1; i < m - 1; i++) {
    for (let j = 1; j < n - 1; j++) {
      if (!uf.isConnected(dummy, i * n + j)) {
        board[i][j] = "X";
      }
    }
  }
};

DFS和BFS的解题思路

  1. 四边的利用DFS或BFS把O都变成特殊字符,比方说A
  2. 循环矩阵按照题目意思把单元格值为O都变成X,同时上面置为特殊字符A的还原为O

DFS实现代码

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solve = function (board) {
  // 1四边的利用BFS把O都变成1
  // 循环矩阵bfs把所有的O变成X
  // bfs和dfs都行吧
  let rows = board.length,
    cols = board[0].length;
  // 方向数组
  let directions = [
    [0, 1],
    [0, -1],
    [-1, 0],
    [1, 0],
  ];
  const dfs = (i, j) => {
    if (
      i < 0 ||
      j < 0 ||
      i >= rows ||
      j >= cols ||
      board[i][j] != "O" ||
      board[i][j] == "A"
    )
      return;
    board[i][j] = "A";
    for (let [x, y] of directions) {
      dfs(i + x, j + y);
    }
  };
  for (let i = 0; i < cols; i++) {
    // 第1行和最后一行
    if (board[0][i] == "O") dfs(0, i);
    if (board[rows - 1][i] == "O") dfs(rows - 1, i);
  }
  for (let i = 0; i < rows; i++) {
    // 第1列和最后一列
    if (board[i][0] == "O") dfs(i, 0);
    if (board[i][cols - 1] == "O") dfs(i, cols - 1);
  }
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] == "A") {
        board[i][j] = "O";
      } else if (board[i][j] == "O") {
        board[i][j] = "X";
      }
    }
  }
};

BFS实现代码

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solve = function (board) {
  // 1四边的利用BFS把O都变成1
  // 循环矩阵bfs把所有的O变成X
  // bfs和dfs都行吧
  let rows = board.length,
    cols = board[0].length;
  // 方向数组
  let directions = [
    [0, 1],
    [0, -1],
    [-1, 0],
    [1, 0],
  ];
  const bfs = (i, j) => {
    let queue = [[i, j]];
    while (queue.length) {
      let size = queue.length;
      while (size--) {
        let [x, y] = queue.shift();
        if (
          x < 0 ||
          y < 0 ||
          x >= rows ||
          y >= cols ||
          board[x][y] != "O" ||
          board[x][y] == "A"
        )
          continue;
        board[x][y] = "A";
        for (let [curI, curJ] of directions) {
          queue.push([curI + x, curJ + y]);
        }
      }
    }
  };
  for (let i = 0; i < cols; i++) {
    // 第1行和最后一行
    if (board[0][i] == "O") bfs(0, i);
    if (board[rows - 1][i] == "O") bfs(rows - 1, i);
  }
  for (let i = 0; i < rows; i++) {
    // 第1列和最后一列
    if (board[i][0] == "O") bfs(i, 0);
    if (board[i][cols - 1] == "O") bfs(i, cols - 1);
  }
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (board[i][j] == "A") {
        board[i][j] = "O";
      } else if (board[i][j] == "O") {
        board[i][j] = "X";
      }
    }
  }
};