前言 我们知道,DFS
通常是在树或者图结构上进行的,而岛屿问题都是网格,能不能用DFS
呢? 可以,记住,凡是网格的都应该想到用DFS
,岛屿问题就是一类典型的网格问题。
题目 给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1: 输入: 输出:1
示例 2: 输入: 输出:3
提示:的 值 为 或
题解 首先我们要清楚DFS
的基本结构,先看简单的二叉树DFS
遍历结构
1 2 3 4 5 6 7 8 9 void traverse (TreeNode* root) { if (root == NULL ) { return ; } traverse (root->left); traverse (root->right); }
可以看到,二叉树的DFS
有两个要素:访问相邻结点、判断base case
。
二叉树的相邻结点非常简单,只有左子结点和右子结点两个。
二叉树本身就是一个递归定义的结构:一棵二叉树,它的左子树和右子树也是一棵二叉树。那么我们的DFS
遍历只需要递归调用左子树和右子树即可。
二叉树遍历的base case
是root == NULL
。
这样一个条件判断其实有两个含义:一方面,这表示 root
指向的子树为空,不需要再往下遍历了。另一方面,在root == NULL
的时候及时返回,可以让后面的root->left
和root->right
操作不会出现空指针异常。
那么对于网格上的DFS
,我们完全可以参考二叉树的DFS
,写出网格DFS
的两个要素。
首先看相邻结点。很明显,网格结构中的格子的相邻结点是上下左右四个,即(row-1, col),(row+1, col),(row, col-1),(row, col+1)
;
然后是base case
。根据二叉树的对应过来,是超出网格范围的格子,即row >= grid.size() || col >= grid[0].size() || row < 0 || col < 0
。
根据分析,可以得出网格DFS
遍历的框架代码:
1 2 3 4 5 6 7 8 9 10 11 void dfsGrid (vector<vector<char >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return ; } dfsGrid (grid, row - 1 , col); dfsGrid (grid, row + 1 , col); dfsGrid (grid, row, col - 1 ); dfsGrid (grid, row, col + 1 ); }
这里有个问题,怎么避免重复值,比如下面这张图,dfsGrid
遍历时会一直在这里不断循环。 简单的方法就是标记已经遍历过的格子。比如岛屿问题,把走过的陆地格子的值改为2。这样就能得到一个网格DFS
遍历的通用框架代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void dfsGrid (vector<vector<char >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return ; } if (grid[row][col] != '1' ) { return ; } grid[row][col] = '2' ; dfsGrid (grid, row - 1 , col); dfsGrid (grid, row + 1 , col); dfsGrid (grid, row, col - 1 ); dfsGrid (grid, row, col + 1 ); }
有了网格DFS
遍历的通用框架,我们只需要用两层for循环遍历整张二维表格中所有的陆地,连续的视为一个岛屿。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public : int numIslands (vector<vector<char >>& grid) { int res = 0 ; for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid[0 ].size (); j++) { if (grid[i][j] == '1' ) { dfsGrid (grid, i, j); res++; } } } return res; } void dfsGrid (vector<vector<char >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return ; } if (grid[row][col] != '1' ) { return ; } grid[row][col] = '2' ; dfsGrid (grid, row - 1 , col); dfsGrid (grid, row + 1 , col); dfsGrid (grid, row, col - 1 ); dfsGrid (grid, row, col + 1 ); } };
题目 给定一个row x col
的二维网格地图grid
,其中:grid[i][j] = 1
表示陆地,grid[i][j] = 0
表示水域。
网格中的格子水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例 1: 输入: 输出:16 解释:它的周长是上面图片中的 16 个黄色的边
示例 2: 输入:grid = [[1]] 输出:4
示例 3: 输入:grid = [[1,0]] 输出:4
提示:为 或
题解 这道题最牛逼的一点是你要想到,岛屿的周长就是岛屿方格和非岛屿方格相邻的边的数量(如下图所示)。也就是说,在DFS
遍历中,从一个岛屿方格走向一个非岛屿方格,就将周长加1。 所以,我们可以修改下网格DFS
遍历的通用框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int dfsGrid (vector<vector<int >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return 1 ; } if (grid[row][col] == 0 ) { return 1 ; } if (grid[row][col] != 1 ) { return 0 ; } grid[row][col] = 2 ; int res = dfsGrid (grid, row - 1 , col) + dfsGrid (grid, row + 1 , col) + dfsGrid (grid, row, col - 1 ) + dfsGrid (grid, row, col + 1 ); return res; }
题目限制只有一个岛屿,那我们计算一个即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public : int islandPerimeter (vector<vector<int >>& grid) { for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid[0 ].size (); j++) { if (grid[i][j] == 1 ) { return dfsGrid (grid, i, j); } } } return 0 ; } int dfsGrid (vector<vector<int >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return 1 ; } if (grid[row][col] == 0 ) { return 1 ; } if (grid[row][col] != 1 ) { return 0 ; } grid[row][col] = 2 ; int res = dfsGrid (grid, row - 1 , col) + dfsGrid (grid, row + 1 , col) + dfsGrid (grid, row, col - 1 ) + dfsGrid (grid, row, col + 1 ); return res; } };
题目 给你一个大小为m x n
的二进制矩阵grid
。 岛屿是由一些相邻的1
(代表土地) 构成的组合,这里的「相邻」要求两个1
必须在水平或者竖直的四个方向上 相邻。你可以假设grid
的四个边缘都被0
(代表水)包围着。 岛屿的面积是岛上值为1
的单元格的数目。 计算并返回grid
中最大的岛屿面积。如果没有岛屿,则返回面积为0
。
示例 1: 输入: 输出:6 解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2: 输入: 输出:0
提示:为 或
题解 从上面两道我们已经知道怎么计算岛屿数量和一个岛屿的周长,这道题是结合了上面两道。 因此我们可以对每个岛屿计算它的面积,最后返回最大的那个面积即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public : int maxAreaOfIsland (vector<vector<int >>& grid) { int res = 0 ; for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid[0 ].size (); j++) { if (grid[i][j] == 1 ) { res = max (dfsGrid (grid, i, j), res); } } } return res; } int dfsGrid (vector<vector<int >>& grid, int row, int col) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return 0 ; } if (grid[row][col] != 1 ) { return 0 ; } grid[row][col] = 2 ; int res = dfsGrid (grid, row - 1 , col) + dfsGrid (grid, row + 1 , col) + dfsGrid (grid, row, col - 1 ) + dfsGrid (grid, row, col + 1 ) + 1 ; return res; } };
题目 给你一个大小为n x n
二进制矩阵grid
。最多 只能将一格0
变成1
。 返回执行此操作后,grid
中最大的岛屿面积是多少? 岛屿由一组上、下、左、右四个方向相连的1
形成。
示例 1: 输入: 输出: 3 解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。
示例 2: 输入: 输出: 4 解释: 将一格0变成1,岛屿的面积扩大为 4。
示例 3: 输入: 输出: 4 解释: 没有0可以让我们变成1,面积依然为 4。
提示:为 或
题解 这道题是第三题的升级版,现在我们可以将一个海洋变成陆地,从而连接两个岛屿。 那我们就需要先统计各个岛屿面积,找到最大的岛屿;然后把一个海洋变成陆地,再统计一遍连接后各个岛屿面积,找到最大的岛屿。
因此需要两次DFS
遍历: 1、划分岛屿,给每个岛屿标号 标号要标什么呢?假设我们在所有的格子上标记出岛屿的面积。然后搜索哪个海洋格子相邻的两个岛屿面积最大。 例如下图中红色方框内的海洋格子,上边、左边都与岛屿相邻,我们可以计算出它变成陆地之后可以连接成的岛屿面积为7 + 1 + 2 = 10
。 然而,这种做法可能遇到一个问题。如下图中红色方框内的海洋格子,它的上边、左边都与岛屿相邻,这时候连接成的岛屿面积难道是7 + 1 + 7 = 15
?显然不是。这两个7来自同一个岛屿,所以填海造陆之后得到的岛屿面积应该只有7 + 1 = 8
。 可以看到,要让算法正确,我们得能区分一个海洋格子相邻的两个7是不是来自同一个岛屿。那么,我们不能在方格中标记岛屿的面积,而应该用map
记录每个岛屿面积,给每个岛屿标记map
的key
。 如下图所示。这样我们就可以发现红色方框内的海洋格子,它的两个相邻的岛屿实际上是同一个。
2、填充海洋,连接四周的岛屿 和上面类似,要遍历每个海洋格子上下左右的格子。又因为我们已经有map
来记录了各个岛屿的面积,所以只需要在遍历时发现是岛屿,加上对应的面积即可,不需要再全部遍历该岛屿的陆地。 要注意的是,我们是将一个海洋变为陆地,所以海洋会占一个面积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class Solution { public : unordered_map<int , int > area; public : int largestIsland (vector<vector<int >>& grid) { int res = 0 ; int index = 2 ; for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid[0 ].size (); j++) { if (grid[i][j] == 1 ) { area[index] = dfsGrid (grid, i, j, index); res = max (res, area[index]); index++; } } } for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid.size (); j++) { if (grid[i][j] == 0 ) { res = max (res, linkland (grid, i, j)); } } } return res; } int dfsGrid (vector<vector<int >>& grid, int row, int col, int index) { if (row >= grid.size () || col >= grid[0 ].size () || row < 0 || col < 0 ) { return 0 ; } if (grid[row][col] != 1 ) { return 0 ; } grid[row][col] = index; int res = dfsGrid (grid, row - 1 , col, index) + dfsGrid (grid, row + 1 , col, index) + dfsGrid (grid, row, col - 1 , index) + dfsGrid (grid, row, col + 1 , index) + 1 ; return res; } int linkland (vector<vector<int >>& grid, int row, int col) { unordered_set<int > around; int linkarea = 1 ; if (row - 1 >= 0 && grid[row - 1 ][col] > 1 ) { around.insert (grid[row - 1 ][col]); } if (row + 1 < grid.size () && grid[row + 1 ][col] > 1 ) { around.insert (grid[row + 1 ][col]); } if (col - 1 >= 0 && grid[row][col - 1 ] > 1 ) { around.insert (grid[row][col - 1 ]); } if (col + 1 < grid.size () && grid[row][col + 1 ] > 1 ) { around.insert (grid[row][col + 1 ]); } for (auto i : around) { linkarea += area[i]; } return linkarea; } };