200.岛屿数量(bfs)

1,029 阅读1分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。

每日刷题第35天 2021.1.31

200. 岛屿数量

题目

  • 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
  • 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
  • 此外,你可以假设该网格的四条边均被水包围。

示例

  • 示例1
输入:grid = [  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1
  • 示例2
输入:grid = [  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

解法

  • 广度优先搜索, 遍历每个节点的上下左右四个方向的节点。
  • 出错原因:wa了一次,是因为我只遍历了每个节点的右边和下边的节点,出错样例入如下。
[["1","1","1"],["0","1","0"],["1","1","1"]]
转换下:
[
  ["1","1","1"],
  ["0","1","0"],
  ["1","1","1"]
]
正确输出结果:1
我自己代码的输出结果:2(出错原因就因为没有遍历(20)节点)

整体的解题思路

  • 创建一个队列queue来实现bfs
  • 创建空的记录数组visited,记录遍历过的节点,无需再次遍历。刚开始的时候,visited数组中的值全部为0(没有被访问过),当每次遍历过后,将visited的值置为1,避免下次再重复遍历。
  • 每次bfs函数执行完成,相当于找到了所有连接在一起的1,返回,此时给结果sum++
  • 因此将所有的1遍历完,就能够得到最终的结果。

类似题型总结

  • 首先明确一下岛屿问题中的网格结构是如何定义的,以方便我们后面的讨论。
  • 网格问题是由 m×n 个小方格组成一个网格,每个小方格与其上下左右四个方格认为是相邻的,要在这样的网格上进行某种搜索。
  • 岛屿问题是一类典型的网格问题。每个格子中的数字可能是 0 或者 1。我们把数字为 0 的格子看成海洋格子,数字为 1 的格子看成陆地格子,这样相邻的陆地格子就连接成一个岛屿。
  • 在这样一个设定下,就出现了各种岛屿问题的变种,包括岛屿的数量、面积、周长等。不过这些问题,基本都可以用DFS遍历来解决。

如何避免重复遍历(新思路)

  • 网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。这是因为,网格结构本质上是一个「图」,我们可以把每个格子看成图中的结点,每个结点有向上下左右的四条边。在图中遍历时,自然可能遇到重复遍历结点。
  • 这时候,DFS 可能会不停地「兜圈子」,永远停不下来
  • 如何避免这样的重复遍历呢?答案是标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:
0 —— 海洋格子
1 —— 陆地格子(未遍历过)
2 —— 陆地格子(已遍历过)
/**
* @param {character[][]} grid
* @return {number}
*/

var numIslands = function(grid) {
  // 图是二维数组
  // 队列为空,表示当前已经找到了终点,num++;
  // 还需要开一个新的二维数组来标记是否visited
  // 记录给的图的长度
  let lenR = grid.length;
  let lenC = grid[0].length;
  let visited = new Array();
  for (let i = 0; i < lenR; i++) {
    visited[i] = new Array();
    for (let j = 0; j < lenC; j++) {
      visited[i][j] = 0;
    }
  }

  // 书写bfs函数
  // 先将跟节点放入到队列中
  let queue = [];
  // 先进先出
  function bfs(i, j) {
    // 旁边的下边,需要判断下范围 (x,y + 1) || (x + 1,y)
    queue.push([i, j]);
    while(queue.length != 0) {
      // console.log('bfs',queue);
      let tempt = queue.shift();
      i = tempt[0];
      j = tempt[1];
      // 队列为空,则找完,直接返回即可
      // 判断相邻的两个方向,对于每次pop出去的数值
      // 将右节点 和 下节点一起放进去,并判断两节点是否越界
      if(j < lenC - 1 && i < lenR) {
        if(grid[i][j + 1] == 1 && visited[i][j + 1] == 0) {
          // 加入到队列中 右边
          // console.log('one',i,j+1,'lenC',lenR,lenC);
          queue.push([i, j + 1]);
          visited[i][j + 1] = 1;
        }
      }
      // console.log('queue',queue);

      if(i < lenR - 1 && j < lenC) {
        // 下边
        if(grid[i + 1][j] == 1 && visited[i + 1][j] == 0) {
          queue.push([i + 1, j]);
          visited[i + 1][j] = 1;
          // console.log('two',i+1,j,'queue',queue);
        }
      }

      // 上边 (x - 1,y)
      if(i - 1 >= 0) {
        if(grid[i - 1][j] == 1 && visited[i - 1][j] == 0) {
          queue.push([i - 1, j]);
          visited[i - 1][j] = 1;
        }
      }

      // 左边 (x, y - 1)
      if(j - 1 >= 0) {
        if(grid[i][j - 1] == 1 && visited[i][j - 1] == 0) {
          queue.push([i, j - 1]);
          visited[i][j - 1] = 1;
        }
      }
    }
  }

  // 0表示:未访问过
  // console.log('initial',visited);
  let num = 0;
  for(let i = 0; i < lenR; i++) {
    for(let j = 0; j < lenC; j++) {
      // 入队列的条件:为`1`且visited == 0
      if(grid[i][j] == 1 && visited[i][j] == 0) {
      // 可以遍历的起点
        bfs(i, j);
        num++;
      }
    }
  }
  return num;
};