Files
leetcode-master/problems/kamacoder/0104.建造最大岛屿.md
程序员Carl 4029a35b21 Merge pull request #2921 from ideallain/master
增加岛屿数量深搜go版本
2025-11-07 17:35:46 +08:00

766 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>
# 104.建造最大岛屿
[卡码网题目链接ACM模式](https://kamacoder.com/problempage.php?pid=1176)
题目描述:
给定一个由 1陆地和 0组成的矩阵你最多可以将矩阵中的一格水变为一块陆地在执行了此操作之后矩阵中最大的岛屿面积是多少。
岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。
输入描述:
第一行包含两个整数 N, M表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0表示岛屿的单元格。
输出描述:
输出一个整数,表示最大的岛屿面积。
输入示例:
```
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
```
输出示例
6
提示信息
![](https://file1.kamacoder.com/i/algo/20240522154055.png)
对于上面的案例,有两个位置可将 0 变成 1使得岛屿的面积最大即 6。
![](https://file1.kamacoder.com/i/algo/20240522154110.png)
数据范围:
1 <= M, N <= 50。
## 思路
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[图论:岛屿问题上难度了! |深搜优先搜索DFS | 广度优先搜索BFS | 卡码网104.建造最大岛屿](https://www.bilibili.com/video/BV1Dn5CzZEw1/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
本题的一个暴力想法,应该是遍历地图尝试 将每一个 0 改成1然后去搜索地图中的最大的岛屿面积。
计算地图的最大面积:遍历地图 + 深搜岛屿,时间复杂度为 n * n。
(其实使用深搜还是广搜都是可以的,其目的就是遍历岛屿做一个标记,相当于染色,那么使用哪个遍历方式都行,以下我用深搜来讲解)
每改变一个0的方格都需要重新计算一个地图的最大面积所以 整体时间复杂度为n^4。
## 优化思路
其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。
只要用一次深搜把每个岛屿的面积记录下来就好。
第一步一次遍历地图得出各个岛屿的面积并做编号记录。可以使用map记录key为岛屿编号value为岛屿面积
第二步再遍历地图遍历0的方格因为要将0变成1并统计该1由0变成的1周边岛屿面积将其相邻面积相加在一起遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。
拿如下地图的岛屿情况来举例: 1为陆地
![](https://file1.kamacoder.com/i/algo/20220829104834.png)
第一步,则遍历地图,并将岛屿的编号和面积都统计好,过程如图所示:
![](https://file1.kamacoder.com/i/algo/20220829105644.png)
本过程代码如下:
```CPP
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int mark) {
if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
visited[x][y] = true; // 标记访问过
grid[x][y] = mark; // 给陆地标记新标签
count++;
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过
dfs(grid, visited, nextx, nexty, mark);
}
}
int largestIsland(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
vector<vector<bool>> visited = vector<vector<bool>>(n, vector<bool>(m, false)); // 标记访问过的点
unordered_map<int ,int> gridNum;
int mark = 2; // 记录每个岛屿的编号
bool isAllGrid = true; // 标记是否整个地图都是陆地
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) isAllGrid = false;
if (!visited[i][j] && grid[i][j] == 1) {
count = 0;
dfs(grid, visited, i, j, mark); // 将与其链接的陆地都标记上 true
gridNum[mark] = count; // 记录每一个岛屿的面积
mark++; // 记录下一个岛屿编号
}
}
}
}
```
这个过程时间复杂度 n * n 。可能有录友想分明是两个for循环下面套这一个dfs时间复杂度怎么回事 n * n呢
其实大家可以仔细看一下代码,**n * n这个方格地图中每个节点我们就遍历一次并不会重复遍历**。
第二步过程如图所示:
![](https://file1.kamacoder.com/i/algo/20220829105249.png)
也就是遍历每一个0的方格并统计其相邻岛屿面积最后取一个最大值。
这个过程的时间复杂度也为 n * n。
所以整个解法的时间复杂度,为 n * n + n * n 也就是 n^2。
当然这里还有一个优化的点,就是 可以不用 visited数组因为有mark来标记所以遍历过的grid[i][j]是不等于1的。
代码如下:
```CPP
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, int x, int y, int mark) {
if (grid[x][y] != 1 || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
grid[x][y] = mark; // 给陆地标记新标签
count++;
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue; // 越界了,直接跳过
dfs(grid, nextx, nexty, mark);
}
}
int main() {
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> grid[i][j];
}
}
unordered_map<int ,int> gridNum;
int mark = 2; // 记录每个岛屿的编号
bool isAllGrid = true; // 标记是否整个地图都是陆地
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) isAllGrid = false;
if (grid[i][j] == 1) {
count = 0;
dfs(grid, i, j, mark); // 将与其链接的陆地都标记上 true
gridNum[mark] = count; // 记录每一个岛屿的面积
mark++; // 记录下一个岛屿编号
}
}
}
```
不过为了让各个变量各司其事代码清晰一些完整代码还是使用visited数组来标记。
最后,整体代码如下:
```CPP
#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
int n, m;
int count;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int mark) {
if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
visited[x][y] = true; // 标记访问过
grid[x][y] = mark; // 给陆地标记新标签
count++;
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue; // 越界了,直接跳过
dfs(grid, visited, nextx, nexty, mark);
}
}
int main() {
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false)); // 标记访问过的点
unordered_map<int ,int> gridNum;
int mark = 2; // 记录每个岛屿的编号
bool isAllGrid = true; // 标记是否整个地图都是陆地
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) isAllGrid = false;
if (!visited[i][j] && grid[i][j] == 1) {
count = 0;
dfs(grid, visited, i, j, mark); // 将与其链接的陆地都标记上 true
gridNum[mark] = count; // 记录每一个岛屿的面积
mark++; // 记录下一个岛屿编号
}
}
}
if (isAllGrid) {
cout << n * m << endl; // 如果都是陆地,返回全面积
return 0; // 结束程序
}
// 以下逻辑是根据添加陆地的位置,计算周边岛屿面积之和
int result = 0; // 记录最后结果
unordered_set<int> visitedGrid; // 标记访问过的岛屿
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
count = 1; // 记录连接之后的岛屿数量
visitedGrid.clear(); // 每次使用时,清空
if (grid[i][j] == 0) {
for (int k = 0; k < 4; k++) {
int neari = i + dir[k][1]; // 计算相邻坐标
int nearj = j + dir[k][0];
if (neari < 0 || neari >= n || nearj < 0 || nearj >= m) continue;
if (visitedGrid.count(grid[neari][nearj])) continue; // 添加过的岛屿不要重复添加
// 把相邻四面的岛屿数量加起来
count += gridNum[grid[neari][nearj]];
visitedGrid.insert(grid[neari][nearj]); // 标记该岛屿已经添加过
}
}
result = max(result, count);
}
}
cout << result << endl;
}
```
## 其他语言版本
### Java
```Java
public class Main {
// 该方法采用 DFS
// 定义全局变量
// 记录每次每个岛屿的面积
static int count;
// 对每个岛屿进行标记
static int mark;
// 定义二维数组表示四个方位
static int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
// DFS 进行搜索,将每个岛屿标记为不同的数字
public static void dfs(int[][] grid, int x, int y, boolean[][] visited) {
// 当遇到边界直接return
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) return;
// 遇到已经访问过的或者遇到海水,直接返回
if (visited[x][y] || grid[x][y] == 0) return;
visited[x][y] = true;
count++;
grid[x][y] = mark;
// 继续向下层搜索
dfs(grid, x, y + 1, visited);
dfs(grid, x, y - 1, visited);
dfs(grid, x + 1, y, visited);
dfs(grid, x - 1, y, visited);
}
public static void main (String[] args) {
// 接收输入
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
int n = sc.nextInt();
int[][] grid = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
grid[i][j] = sc.nextInt();
}
}
// 初始化mark变量从2开始区别于0水1岛屿
mark = 2;
// 定义二位boolean数组记录该位置是否被访问
boolean[][] visited = new boolean[m][n];
// 定义一个HashMap记录某片岛屿的标记号和面积
HashMap<Integer, Integer> getSize = new HashMap<>();
// 定义一个HashSet用来判断某一位置水四周是否存在不同标记编号的岛屿
HashSet<Integer> set = new HashSet<>();
// 定义一个boolean变量看看DFS之后是否全是岛屿
boolean isAllIsland = true;
// 遍历二维数组进行DFS搜索标记每片岛屿的编号记录对应的面积
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) isAllIsland = false;
if (grid[i][j] == 1) {
count = 0;
dfs(grid, i, j, visited);
getSize.put(mark, count);
mark++;
}
}
}
int result = 0;
if (isAllIsland) result = m * n;
// 对标记完的grid继续遍历判断每个水位置四周是否有岛屿并记录下四周不同相邻岛屿面积之和
// 每次计算完一个水位置周围可能存在的岛屿面积之和更新下result变量
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0) {
set.clear();
// 当前水位置变更为岛屿所以初始化为1
int curSize = 1;
for (int[] dir : dirs) {
int curRow = i + dir[0];
int curCol = j + dir[1];
if (curRow < 0 || curRow >= m || curCol < 0 || curCol >= n) continue;
int curMark = grid[curRow][curCol];
// 如果当前相邻的岛屿已经遍历过或者HashMap中不存在这个编号继续搜索
if (set.contains(curMark) || !getSize.containsKey(curMark)) continue;
set.add(curMark);
curSize += getSize.get(curMark);
}
result = Math.max(result, curSize);
}
}
}
// 打印结果
System.out.println(result);
}
}
```
### Python
#### BFS
```Python
from typing import List
from collections import defaultdict
class Solution:
def __init__(self):
self.direction = [(1,0),(-1,0),(0,1),(0,-1)]
self.res = 0
self.count = 0
self.idx = 1
self.count_area = defaultdict(int)
def max_area_island(self, grid: List[List[int]]) -> int:
if not grid or len(grid) == 0 or len(grid[0]) == 0:
return 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
self.count = 0
self.idx += 1
self.dfs(grid,i,j)
# print(grid)
self.check_area(grid)
# print(self.count_area)
if self.check_largest_connect_island(grid=grid):
return self.res + 1
return max(self.count_area.values())
def dfs(self,grid,row,col):
grid[row][col] = self.idx
self.count += 1
for dr,dc in self.direction:
_row = dr + row
_col = dc + col
if 0<=_row<len(grid) and 0<=_col<len(grid[0]) and grid[_row][_col] == 1:
self.dfs(grid,_row,_col)
return
def check_area(self,grid):
m, n = len(grid), len(grid[0])
for row in range(m):
for col in range(n):
self.count_area[grid[row][col]] = self.count_area.get(grid[row][col],0) + 1
return
def check_largest_connect_island(self,grid):
m, n = len(grid), len(grid[0])
has_connect = False
for row in range(m):
for col in range(n):
if grid[row][col] == 0:
has_connect = True
area = 0
visited = set()
for dr, dc in self.direction:
_row = row + dr
_col = col + dc
if 0<=_row<len(grid) and 0<=_col<len(grid[0]) and grid[_row][_col] != 0 and grid[_row][_col] not in visited:
visited.add(grid[_row][_col])
area += self.count_area[grid[_row][_col]]
self.res = max(self.res, area)
return has_connect
def main():
m, n = map(int, input().split())
grid = []
for i in range(m):
grid.append(list(map(int,input().split())))
sol = Solution()
print(sol.max_area_island(grid))
if __name__ == '__main__':
main()
```
```Python
import collections
directions = [[-1, 0], [0, 1], [0, -1], [1, 0]]
area = 0
def dfs(i, j, grid, visited, num):
global area
if visited[i][j]:
return
visited[i][j] = True
grid[i][j] = num # 标记岛屿号码
area += 1
for x, y in directions:
new_x = i + x
new_y = j + y
if (
0 <= new_x < len(grid)
and 0 <= new_y < len(grid[0])
and grid[new_x][new_y] == "1"
):
dfs(new_x, new_y, grid, visited, num)
def main():
global area
N, M = map(int, input().strip().split())
grid = []
for i in range(N):
grid.append(input().strip().split())
visited = [[False] * M for _ in range(N)]
rec = collections.defaultdict(int)
cnt = 2
for i in range(N):
for j in range(M):
if grid[i][j] == "1":
area = 0
dfs(i, j, grid, visited, cnt)
rec[cnt] = area # 纪录岛屿面积
cnt += 1
res = 0
for i in range(N):
for j in range(M):
if grid[i][j] == "0":
max_island = 1 # 将水变为陆地故从1开始计数
v = set()
for x, y in directions:
new_x = i + x
new_y = j + y
if (
0 <= new_x < len(grid)
and 0 <= new_y < len(grid[0])
and grid[new_x][new_y] != "0"
and grid[new_x][new_y] not in v # 岛屿不可重复
):
max_island += rec[grid[new_x][new_y]]
v.add(grid[new_x][new_y])
res = max(res, max_island)
if res == 0:
return max(rec.values()) # 无水的情况
return res
if __name__ == "__main__":
print(main())
```
### Go
```go
package main
import "fmt"
func main() {
var m, n int
fmt.Scan(&m, &n)
grid := make([][]int, m)
for i := range grid {
grid[i] = make([]int, n)
for j := range grid[i] {
fmt.Scan(&grid[i][j])
}
}
sum := buildMaxIsland(grid)
fmt.Println(sum)
}
func buildMaxIsland(grid [][]int) int {
result := 0
if grid == nil || len(grid) == 0 {
return result
}
hashMap := make(map[int]int)
islandArea := 0
gridMark := 2
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[0]); j++ {
if grid[i][j] == 1 {
islandArea = 0
visitGrid4(grid, i, j, &islandArea, gridMark)
hashMap[gridMark] = islandArea
if islandArea > result {
result = islandArea
}
gridMark++
}
}
}
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[0]); j++ {
if grid[i][j] == 0 {
sum := findNearByIsland(grid, i, j, hashMap)
if sum > result {
result = sum
}
}
}
}
return result
}
func visitGrid4(grid [][]int, x int, y int, preValue *int, gridMark int) {
if x < 0 || y < 0 || x >= len(grid) || y >= len(grid[0]) {
return
}
//可以省略掉visited的原因是因为grid的每个位置如果被遍历过就会被标记为gridMark能识别出来是不是被遍历过
if grid[x][y] != 1 {
return
}
// visited[x][y] = true
grid[x][y] = gridMark
*preValue++
visitGrid4(grid, x+1, y, preValue, gridMark)
visitGrid4(grid, x-1, y, preValue, gridMark)
visitGrid4(grid, x, y+1, preValue, gridMark)
visitGrid4(grid, x, y-1, preValue, gridMark)
}
func findNearByIsland(grid [][]int, x, y int, hashMap map[int]int) int {
markSet := make(map[int]bool)
sum := 1
coordinate := [][]int{{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}}
for _, arr := range coordinate {
if arr[0] < 0 || arr[1] < 0 || arr[0] >= len(grid) || arr[1] >= len(grid[0]) {
continue
}
if grid[arr[0]][arr[1]] == 0 {
continue
}
gridMark := grid[arr[0]][arr[1]]
if !markSet[gridMark] {
markSet[gridMark] = true
sum += hashMap[gridMark]
}
}
return sum
}
```
### Rust
### JavaScript
```javascript
const r1 = require('readline').createInterface({ input: process.stdin });
// 创建readline接口
let iter = r1[Symbol.asyncIterator]();
// 创建异步迭代器
const readline = async () => (await iter.next()).value;
let graph // 地图
let N, M // 地图大小
let visited // 访问过的节点, 标记岛屿
const dir = [[0, 1], [1, 0], [0, -1], [-1, 0]] //方向
let count = 0 // 统计岛屿面积
let areaMap = new Map() // 存储岛屿面积
// 读取输入,初始化地图
const initGraph = async () => {
let line = await readline();
[N, M] = line.split(' ').map(Number);
graph = new Array(N).fill(0).map(() => new Array(M).fill(0))
visited = new Array(N).fill(0).map(() => new Array(M).fill(0))
for (let i = 0; i < N; i++) {
line = await readline()
line = line.split(' ').map(Number)
for (let j = 0; j < M; j++) {
graph[i][j] = line[j]
}
}
}
/**
* @description: 从xy开始深度优先遍历地图
* @param {*} graph 地图
* @param {*} visited 可访问节点
* @param {*} x 开始搜索节点的下标
* @param {*} y 开始搜索节点的下标
* @param {*} mark 当前岛屿的标记
* @return {*}
*/
const dfs = (graph, visited, x, y, mark) => {
if (visited[x][y] != 0) return
visited[x][y] = mark
count++
for (let i = 0; i < 4; i++) {
let nextx = x + dir[i][0]
let nexty = y + dir[i][1]
if (nextx < 0 || nextx >= N || nexty < 0 || nexty >= M) continue //越界, 跳过
// 已访问过, 或者是海洋, 跳过
if (visited[nextx][nexty] != 0 || graph[nextx][nexty] == 0) continue
dfs(graph, visited, nextx, nexty, mark)
}
}
(async function () {
// 读取输入,初始化地图
await initGraph()
let isAllLand = true //标记整个地图都是陆地
let mark = 2 // 标记岛屿
for (let i = 0; i < N; i++) {
for (let j = 0; j < M; j++) {
if (graph[i][j] == 0 && isAllLand) isAllLand = false
if (graph[i][j] === 1 && visited[i][j] === 0) {
count = 0
dfs(graph, visited, i, j, mark)
areaMap.set(mark, count)
mark++
}
}
}
// 如果全是陆地, 直接返回面积
if (isAllLand) {
console.log(N * M);
return
}
let result = 0 // 记录最后结果
let visitedIsland = new Map() //标记访问过的岛屿, 因为海洋四周可能是同一个岛屿, 需要标记避免重复统计面积
for (let i = 0; i < N; i++) {
for (let j = 0; j < M; j++) {
if (visited[i][j] === 0) {
count = 1 // 记录连接之后的岛屿数量
visitedIsland.clear() // 每次使用时,清空
// 计算海洋周围岛屿面积
for (let m = 0; m < 4; m++) {
const nextx = i + dir[m][0]
const nexty = j + dir[m][1]
if (nextx < 0 || nextx >= N || nexty < 0 || nexty >= M) continue //越界, 跳过
const island = visited[nextx][nexty]
if (island == 0 || visitedIsland.get(island)) continue// 四周是海洋或者访问过的陆地 跳过
// 标记为访问, 计算面积
visitedIsland.set(island, true)
count += areaMap.get(visited[nextx][nexty])
}
result = Math.max(result, count)
}
}
}
console.log(result);
})()
```
### TypeScript
### PhP
### Swift
### Scala
### C#
### Dart
### C