「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。
每日刷题第35天 2021.1.31
200. 岛屿数量
- leetcode原题链接:leetcode-cn.com/problems/nu…
- 难度:中等
- 方法:bfs(广度优先搜索)
题目
- 给你一个由 '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.lengthn == grid[i].length1 <= m, n <= 300grid[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(出错原因就因为没有遍历(2,0)节点)
整体的解题思路
- 创建一个队列
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;
};