发布:2021年9月8日13:46:59
给你一个由 ‘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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-islands
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
提示:
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 300
- grid[i][j] 的值为 ‘0’ 或 ‘1’
更新:2021年9月8日17:03:34
参考:【算法-LeetCode】695. 岛屿的最大面积(深度优先搜索/DFS)_赖念安的博客-CSDN博客
上面这是我碰到的另一道岛屿问题。总体思路和本题差不多。
【更新结束】
这是我在LeetCode上碰到的第一道岛屿问题。一开始没想到用DFS,而是用动态规划和回溯之类的。后来感觉好像不大对劲,总感觉自己越走越偏了,由于有了前面做题的经验,我也不强求纯靠自己想出正确的方向了,如果已经经过了一番思考还是没有很好的思路,我就回去看看【相关标签】里的提示,看看这道题用到了什么方法。
我看到要用DFS,心里还是没有谱,因为在我的固有印象中,DFS是用于二叉树或是图的遍历的,运用在网格中的话我还是第一次见。但是慢慢地也有了相关的雏形,因为之前面试的时候碰到过一道找路径的题,也是放在网格中,当时题目有提示用递归之类的方法解答,于是我就开始了顺着这个方向开始思考。
一番研究后,我发现这类的岛屿问题其实都有差不多的套路。
①首先我们应该知道寻找岛屿的过程是基于对二维数组所形成的网格 grid
的遍历。在对这个网格 grid
进行遍历时,我们应该判断当前访问的网格点是否已经被访问过,如果是已经访问过的网格点,就可以立马跳过对它的后续访问。于是这就意味着我们需要一个与这个网格 grid
等长等宽的二维数组 visited
来记录我们对 grid
的访问情况。visited
数组中的每个元素都是布尔值, visited[i][j] === false
则说明网格点 grid[i][j]
没有被访问过,反之则为已被访问过。
②第二个关键点就是对当前遍历的网格点的相邻点(上下左右四个点)的递归遍历。如果当前遍历的网格点是未被访问过的且该网格点代表的是陆地,那么就要以该点为中心,对其上下左右四个点进行访问,而访问这些相邻点时也是同样的访问方法:即访问其上下左右四个相邻点,这就是深度优先搜索的精髓所在。而实现这种访问方法最直观的方法就是通过递归函数。
③在递归遍历网格点时,首先就应该判断当前网格点是否是有效的,也就是是否满足下面三个条件:
inGrid()
函数完成判断并返回一个布尔值,如果为 false
则说明超出网格范围,反之则在范围内);不满足其中任意一个条件都应该结束递归。
④当对一个网格点的DFS完成之后,说明已经完成了对一个以 grid[i][j]
为中心的岛屿的探索。这时应当立即让 result
值加一。
注意,
gridDFS(grid, i, j)
函数是放在一个if
判断中的,因为只有在当前网格点未被访问且代表陆地时才有探索的价值(或者说才需要我们用递归函数去探索其周围情况)。
⑤当网格中所有陆地点都被访问过后则将最终累加的 result
值返回。
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function (grid) {
// result用于存储当前所探索到的岛屿数量,遍历完所有网格点后,result就是我们想要的结果
let result = 0;
// 二维数组visited用于记录程序对grid网格的访问情况,元素初始值均为false
let visited = Array.from({ length: grid.length }).map(
() => Array.from({ length: grid[0].length }).fill(false)
);
// 下面的双层for循环用于遍历grid网格中的每一个点
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[0].length; j++) {
// 如果当前遍历的点未被访问且代表的是陆地,则以其为中心进行探索
if (!visited[i][j] && grid[i][j] === '1') {
gridDFS(grid, i, j);
// gridDFS返回后,说明完成了对一个以grid[i][j]为中心的岛屿的探索,结果值加1
result++;
}
}
}
// 对grid中的所有网格点遍历完后,result中存储的就是最终的岛屿数量,将其返回
return result;
// gridDFS用于探索以grid[row][col]为中心的陆地
function gridDFS(grid, row, col) {
// 递归访问之前应该先判断当前网格点的有效性
if (!inGrid(grid, row, col) || visited[row][col] || grid[row][col] === '0') {
return;
}
// 将当前访问的网格点的访问情况置为已访问
visited[row][col] = true;
// 分别对当前网格点的上下左右四个相邻点进行递归访问
gridDFS(grid, row - 1, col);
gridDFS(grid, row + 1, col);
gridDFS(grid, row, col - 1);
gridDFS(grid, row, col + 1);
}
// inGrid用于判断grid[row][col]是否在gird网格中
function inGrid(grid, row, col) {
return 0 <= row && row < grid.length && 0 <= col && col < grid[0].length;
}
};
提交记录
49 / 49 个通过测试用例
状态:通过
执行用时:104 ms, 在所有 JavaScript 提交中击败了15.08%的用户
内存消耗:41.3 MB, 在所有 JavaScript 提交中击败了18.69%的用户
时间:2021/09/08 13:51
可以看到这种解法的时间表现和空间表现都不怎么好。我觉得主要还是因为递归的性能太差了,没办法。
其实这道题在空间消耗方面可以进行一定的优化,因为我们只需要访问代表陆地的网格点,所以只要当前网格点的值为 0
,我们一律不进行访问。那既然这样,我们大可以将访问过的网格点的值由原来的 1
改为 0
。这样的话就完全可以省去 visited
数组的空间开销,不过同时也需要对程序中相应的判断逻辑做一点修改就是了。
但是这种方法需要改变原始数据,我觉得还是最好不要那样,所以就没有用这种方法提交,但是空间表现一定比当前的好。
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年9月8日13:56:40
参考: 岛屿数量 - 岛屿数量 - 力扣(LeetCode)
【更新结束】
更新:2021年9月8日13:57:03
参考:岛屿类问题的通用解法、DFS 遍历框架 - 岛屿数量 - 力扣(LeetCode)
参考:200. 岛屿数量(DFS / BFS) - 岛屿数量 - 力扣(LeetCode)
参考:岛屿类问题通用解法:DFS_Tim_Coder的博客-CSDN博客_岛屿问题dfs
参考:深度优先搜索dfs《岛屿问题》(共四题)_wuyouyin123的博客-CSDN博客_岛屿问题dfs
参考:基本算法——深度优先搜索(DFS)和广度优先搜索(BFS) - 简书
更新:2021年9月8日17:03:34
参考:【算法-LeetCode】695. 岛屿的最大面积(深度优先搜索/DFS)_赖念安的博客-CSDN博客