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
思路:
- dfs
核心思路就是利用dfs算法遍历二维数组。
将二维数组的每一个元素看作一个节点,它相邻的上下左右元素就是它的相邻节点。这样就抽象出一个网状图。然后根据二叉树的dfs写出二维数组的dfs框架
// 二叉树遍历框架
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
}
// 二维矩阵遍历框架
void dfs(int[][] grid, int i, int j, boolean[][] visited) {
int m = grid.length, n = grid[0].length;
if (i < 0 || j < 0 || i >= m || j >= n) {
// 超出索引边界
return;
}
if (visited[i][j]) {
// 已遍历过 (i, j)
return;
}
// 进入节点 (i, j)
visited[i][j] = true;
dfs(grid, i - 1, j); // 上
dfs(grid, i + 1, j); // 下
dfs(grid, i, j - 1); // 左
dfs(grid, i, j + 1); // 右
}
基本思路是这样的。我们依次遍历数组中的每一个节点
- 若
grid[i][j] == '0'
,跳过,不做任何操作; - 若
grid[i][j] == '1'
,岛屿数量+1,并就寻找所有值为1的和它相连的节点。注意,在寻找的过程中,需要使用一个备忘录visited二维数组来记录当前节点是否被遍历过,防止走回头路,若遍历过,即visited[i][j] == true
,则返回,不再对这个节点进行dfs寻找关联节点操作。
实际上可以在寻找的过程中将该岛慢慢淹了,这也可以防止走回头路,不需要开辟额外的空间。所谓将岛屿淹了的意思就是,若当前节点为'1'
,则在dfs中将这个节点的值设置为'0'
,然后将它关联的、值为'1'
的节点也"淹了"。
class Solution {
private int m = 0;
private int n = 0;
public int numIslands(char[][] grid) {
m = grid.length;
n = grid[0].length;
int res = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j] == '1'){
//岛屿数量+1
res++;
//淹没这个岛屿
dfs(grid, i, j);
}
}
}
return res;
}
//方向数组
private int[][] dirs = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private void dfs(char[][] chars, int row, int col){
//索引越界检查
if(row < 0 || col < 0 || row >= m || col >= n){
return;
}
//
if(chars[row][col] == '1'){
//淹没当前陆地
chars[row][col] = '0';
} else {
//已经是海水了,直接返回
return;
}
//淹没相邻陆地
for(int[] dir: helper){
int nextRow = row + dir[0];
int nextCol = col + dir[1];
dfs(chars, nextRow, nextCol);
}
}
}
1254.统计封闭岛屿的数目
问题:有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。
我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。
如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。
请返回封闭岛屿的数目。
示例:
输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
输出:2
解释:
灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。
思路:这道题属于200.岛屿数量的变种。封闭岛屿其实就是在前面【岛屿数量】这道题的基础上 排除数组边界也就是靠边的岛屿 剩下的就是封闭岛屿了。我们只需要将边界岛屿淹掉,剩下的就和【岛屿数量】这道题一样了。
需要注意的是,这道题是用整数表示海水和陆地,且0
表示陆地,1
表示海水。
代码如下:
class Solution {
public int closedIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
//淹掉第一行和最后一行的 n表示列数
for(int j = 0; j < n; ++j){
dfs(grid, 0, j);
dfs(grid, m - 1, j);
}
//淹掉第一列和最后一列 m表示列数
for(int i = 1; i < m; ++i){
dfs(grid, i, 0);
dfs(grid, i, n - 1);
}
int res = 0;
for(int i = 1; i < m - 1; ++i){
for(int j = 1; j < n - 1; ++j){
if(grid[i][j] == 0){
res++;
dfs(grid, i, j);
}
}
}
return res;
}
private int[][] dirs = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private void dfs(int[][] grid, int row, int col){
if(row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
return;
}
if(grid[row][col] == 1){
return;
} else {
grid[row][col] = 1;
}
for(int[] dir: dirs){
int nextRow = row + dir[0];
int nextCol = col + dir[1];
dfs(grid, nextRow, nextCol);
}
}
}
1020.飞地的数量
问题:给出一个二维数组 A,每个单元格为 0(代表海)或 1(代表陆地)。
移动是指在陆地上从一个地方走到另一个地方(朝四个方向之一)或离开网格的边界。
返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。
示例1:
输入:[[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:
有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
思路:这道题的思路和1254.统计封闭岛屿的数目基本一致,都需要淹掉靠边的陆地,不同的是【统计封闭岛屿的数目】需要求岛屿的数目,而这道题需要求的是面积。
我们将靠边的岛屿淹没之后,然后再遍历这个二维数组,统计出1
的个数,即为【飞地的数量】
class Solution {
public int numEnclaves(int[][] grid) {
int m = grid.length, n = grid[0].length;
//淹掉第一行和最后一行的 注意:n表示列数
for(int j = 0; j < n; ++j){
dfs(grid, 0, j);
dfs(grid, m - 1, j);
}
//淹掉第一列和最后一列 注意:m表示列数
for(int i = 1; i < m; ++i){
dfs(grid, i, 0);
dfs(grid, i, n - 1);
}
int res = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j] == 1){
res += 1;
}
}
}
return res;
}
private int[][] dirs = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private void dfs(int[][] grid, int row, int col){
if(row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
return;
}
if(grid[row][col] == 0){
//海水
return;
} else{
//陆地 淹掉
grid[row][col] = 0;
}
for(int[] dir: dirs){
int nextRow = row + dir[0];
int nextCol = col + dir[1];
dfs(grid, nextRow, nextCol);
}
}
}
695.岛屿的最大面积
问题:给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0
示例1:
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
思路:思路和200.岛屿数量一样,只需要再淹没岛屿的时候记录这个岛屿的面积即可。具体做法就是为dfs
函数设置返回值。
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
int res = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j] == 1){
int temp = dfs(grid, i, j);
res = Math.max(res, temp);
}
}
}
return res;
}
private int dfs(int[][] grid, int row, int col){
if(row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
return 0;
}
if(grid[row][col] == 0){
return 0;
} else {
grid[row][col] = 0;
}
int res = 1;
res += dfs(grid, row, col + 1) + dfs(grid, row, col - 1) + dfs(grid, row + 1, col) + dfs(grid, row - 1, col);
return res;
}
}
这里还有个偷懒的做法:设置一个成员变量,用来记录每个岛屿的面积。然后与上一个岛屿面积比较,依次循环,直到遍历完整个二维数组。
class Solution {
private int temp = 0;
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
int res = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j] == 1){
dfs(grid, i, j);
res = Math.max(res, temp);
temp = 0;
}
}
}
return res;
}
private void dfs(int[][] grid, int row, int col){
if(row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
return;
}
if(grid[row][col] == 0){
return;
} else {
grid[row][col] = 0;
temp++;
}
dfs(grid, row, col + 1);
dfs(grid, row, col - 1);
dfs(grid, row + 1, col);
dfs(grid, row - 1, col);
}
}
1905.统计子岛屿
问题:给你两个m x n
的二进制矩阵grid1
和grid2
,它们只包含0
(表示水域)和 1
(表示陆地)。一个 岛屿 是由 四个方向 (水平或者竖直)上相邻的 1
组成的区域。任何矩阵以外的区域都视为水域。
如果 grid2 的一个岛屿,被 grid1 的一个岛屿 完全 包含,也就是说 grid2 中该岛屿的每一个格子都被 grid1 中同一个岛屿完全包含,那么我们称 grid2 中的这个岛屿为 子岛屿 。
请你返回 grid2 中 子岛屿 的 数目 。
示例:
输入:grid1 = [[1,1,1,0,0],[0,1,1,1,1],[0,0,0,0,0],[1,0,0,0,0],[1,1,0,1,1]], grid2 = [[1,1,1,0,0],[0,0,1,1,1],[0,1,0,0,0],[1,0,1,1,0],[0,1,0,1,0]]
输出:3
解释:如上图所示,左边为 grid1 ,右边为 grid2 。
grid2 中标红的 1 区域是子岛屿,总共有 3 个子岛屿。
思路:首先需要明白: 子岛屿的概念:当grid2
中的岛屿的所有陆地在grid1
中也是陆地,那么这个岛屿就是grid1
中某个岛屿的子岛屿。
反过来说,当grid2
中的某一块陆地在grid1中的对应位置是海水,那么这块陆地所在的岛屿肯定不会是grid1
中某块岛屿的子岛屿了。
所以,基本思路就是:
- 若
grid2
中的某一块陆地在grid1
中的对应位置是海水时,即grid1[i][j] == 0 && grid2[i][j] == 1
,就将这块陆地所在的岛屿淹掉,那么剩下的岛屿就是子岛屿,然后再统计其数量,即为所求。代码如下:
class Solution {
public int countSubIslands(int[][] grid1, int[][] grid2) {
int m = grid2.length, n = grid2[0].length;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid1[i][j] == 0 && grid2[i][j] == 1){
dfs(grid2, i, j);
}
}
}
int res = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid2[i][j] == 1){
res++;
dfs(grid2, i, j);
}
}
}
return res;
}
int[][] dirs = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private void dfs(int[][] grid, int row, int col){
//索引越界检查
if(row < 0 || col < 0 || row >= grid.length || col >= grid[0].length){
return;
}
if(grid[row][col] == 0){
return;
} else {
grid[row][col] = 0;
}
for(int[] dir: dirs){
int nextRow = row + dir[0];
int nextCol = col + dir[1];
dfs(grid, nextRow, nextCol);
}
}
}
整理思路,记录博客,以便复习。若有误,望指正~