建造最大岛屿
题目描述:
给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。
岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。
测试样例
输入描述:
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述:
输出一个整数,表示最大的岛屿面积。
输入示例:
bash
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例:
bash
6
提示信息:
对于上述输出,有两个位置 [ 1, 2 ], [ 2, 1 ] 可以使最后岛屿面积最大为6。
数据范围:
1 <= M, N <= 50。
思路:
最直观暴力想法:
直接遍历矩阵中的每个元素 0 进行替换为 1,然后调用 dfs / bfs 搜索陆地的面积来确定最大的陆地面积。(以 dfs 为例子)
优化思路:
遍历矩阵需要 n*m 的时间复杂度,遇到元素 0 还需要每次调用一次 dfs 进行递归搜索陆地面积大小,很费时间。
岛屿是通过水平方向或垂直方向上相邻的陆地连接而成的,所以,如果我们把由 0 变 1 的那个岛屿的四个方向分别搜索一遍,看是否与已有岛屿相邻,如果相邻,就可以直接将岛屿的面积相加,这样可以省去很多重复的 dfs 搜索岛屿操作,我们只需要提前将原岛屿标记起来并且存储它们的面积就可以实现这个操作。
即先进行 dfs 找出所有岛屿并做标记(标记+面积:[2, 4],表示将岛屿标记为2,它的面积为4)。之后遍历矩阵的每一个 0 ,将其变为 1 ,根据四个方向判断它是否与四周岛屿相连,如果相连,则计算相连后的总面积。
代码实现
代码的实现采用Java语言
java
import java.util.*;
public class Main {
//图的矩阵存储
public static int[][] map;
//dfs辅助数组
public static boolean[][] visited;
//行,列
public static int n,m;
//定义四个方向的辅助数组
public static int[] dirx = {0,0,1,-1};
public static int[] diry = {1,-1,0,0};
//存储岛屿编号key+面积val
public static Map<Integer,Integer> area =new HashMap<>();
public static int count = 2; //编号(可直接选择,从2开始是为了与最开始的1区分开,这样更好分辨)
public static int tempArea; //面积
//最大面积
public static int maxArea = 0;
public static void main(String[] args) {
//建造最大岛屿:把矩阵中海洋0变成陆地1,只能改变一个元素,求最大的岛屿面积
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
map = new int[n][m];
visited = new boolean[n][m];
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
map[i][j] = sc.nextInt();
}
}
//求各个岛屿面积并编号记录
for (int i = 0;i < n;i++){
for (int j = 0;j < m;j++){
if (map[i][j]==1 && visited[i][j]==false){
tempArea=0;
dfs(i,j);
//每次调用一次dfs可获得一个岛屿面积,将岛屿面积与编号存储起来
area.put(count,tempArea);
maxArea = Math.max(maxArea,tempArea);
//切换不同编号
count++;
}
}
}
//遍历海洋,并判断改变后四个方向是否有岛屿相邻,有则将相应岛屿面积与当前岛屿面积进行累加
for (int i = 0;i < n;i++){
for (int j = 0; j < m; j++) {
if (map[i][j]==0){
//当前建造的一个陆地(面积为1)
tempArea = 1;
//辅助数组:防止出现特殊情况重复叠加某一个岛屿
Set<Integer> set = new HashSet<>(area.keySet());
for (int d=0;d<4;d++){
int nextx = i+dirx[d];
int nexty = j+diry[d];
if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
if (map[nextx][nexty] > 1 && set.contains(map[nextx][nexty])){
//map[x][y]为编号,考虑面积不能重复加
tempArea += area.get(map[nextx][nexty]);
set.remove(map[nextx][nexty]);
}
}
maxArea = Math.max(maxArea,tempArea);
}
}
}
//输出结果
System.out.println(maxArea);
}
private static void dfs(int x, int y) {
//标记or编号:直接在原map进行修改,将相同的岛屿的各个元素值改变为编号值,最终将编号+面积存入map中
//map也必须要修改,这样当0改变为1时才可以判断相邻的是哪个岛屿
map[x][y] = count;
tempArea++;
for (int d=0;d<4;d++){
int nextx = x+dirx[d];
int nexty = y+diry[d];
//判断是否越界
if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
if (map[nextx][nexty]==1 && visited[nextx][nexty]==false){
dfs(nextx,nexty);
}
}
}
}
以上就是我对这道题的解法和一些思考啦,如果大家有更优的思路、更简洁的实现,或者发现我哪里考虑不周、代码有问题,欢迎一起交流探讨~