如果你一点也不了解什么是DFS(深度优先搜索),建议看一下这一篇LeetCode岛屿问题DFS
827.最大人工岛
给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。
返回执行此操作后,grid 中最大的岛屿面积是多少?
岛屿 由一组上、下、左、右四个方向相连的 1 形成。
来源:力扣(LeetCode)
我们以下图来分析:
思路
我们不难想到,这一题主要就是改变一个海洋’0’变成陆地’1’,我们可以计算每个陆地的大小,然后在其周围寻找是否有相邻的陆地,相加再判断其大小是否是最大的,这样一个一个暴力遍历,就可以AC了。
我们首先要写一个搜寻该陆地的大小:
int max_size(vector<vector<int>>& grid,int i,int j,int n)
{
if(i<0 || i>=grid.size() || j<0 || j>=grid.size() || grid[i][j]!=1)
{
return 0;
}
grid[i][j]=n;
return 1 + max_size(grid,i+1,j,n)+
max_size(grid,i-1,j,n)+
max_size(grid,i,j+1,n)+
max_size(grid,i,j-1,n);
}
这里我们将已经遍历过的陆地更改为数字也是有讲究的:
假如一开始是这样的:
如果我们直接以它们的最大面积的大小来代替它们的陆地编号比如:
但是下面情况我们不好去处理:
所以,我们可以将更改的陆地数字作为下标,面积大小存储与数组中,与下标对应,这样我们就能很好处理上述,一块陆地计算两次情况。
处理边界
接下来我们就
- 要么继续遍历陆地格子,然后在其判断边界是否为海洋格子的时候进行处理
- 要么我们直接遍历海洋格子,然后在海洋格子的四周搜寻陆地
我的建议是遍历海洋格子,因为要继续遍历陆地格子,那么我们又要进DFS递归,这种递归的边界判断很难把握。
int judge(vector<vector<int>>& grid,vector<int>& arr,int i,int j)
{
int sum=0;
vector<int> _arr;
if(tof(grid,i-1,j) && find(_arr.begin(),_arr.end(),grid[i-1][j])==_arr.end() && grid[i-1][j]!=0)
{
sum+=arr[grid[i-1][j]-2];
_arr.push_back(grid[i-1][j]);
}
if(tof(grid,i+1,j) && find(_arr.begin(),_arr.end(),grid[i+1][j])==_arr.end() && grid[i+1][j]!=0)
{
sum+=arr[grid[i+1][j]-2];
_arr.push_back(grid[i+1][j]);
}
if(tof(grid,i,j-1) && find(_arr.begin(),_arr.end(),grid[i][j-1])==_arr.end() && grid[i][j-1]!=0)
{
sum+=arr[grid[i][j-1]-2];
_arr.push_back(grid[i][j-1]);
}
if(tof(grid,i,j+1) && find(_arr.begin(),_arr.end(),grid[i][j+1])==_arr.end() && grid[i][j+1]!=0)
{
sum+=arr[grid[i][j+1]-2];
_arr.push_back(grid[i][j+1]);
}
return sum;
}
我的思路就是直接暴力搜索上下左右,然后将已加过的陆地存入临时数组来判断,避免增加重复陆地的情况。
最终代码:
class Solution {
public:
//DFS返回面积
int max_size(vector<vector<int>>& grid,int i,int j,int n)
{
if(i<0 || i>=grid.size() || j<0 || j>=grid.size() || grid[i][j]!=1)
{
return 0;
}
grid[i][j]=n;
return 1 + max_size(grid,i+1,j,n)+
max_size(grid,i-1,j,n)+
max_size(grid,i,j+1,n)+
max_size(grid,i,j-1,n);
}
//判断海洋四周面积总和
int judge(vector<vector<int>>& grid,vector<int>& arr,int i,int j)
{
int sum=0;
vector<int> _arr;
if(tof(grid,i-1,j) && find(_arr.begin(),_arr.end(),grid[i-1][j])==_arr.end() && grid[i-1][j]!=0)
{
sum+=arr[grid[i-1][j]-2];
_arr.push_back(grid[i-1][j]);
}
if(tof(grid,i+1,j) && find(_arr.begin(),_arr.end(),grid[i+1][j])==_arr.end() && grid[i+1][j]!=0)
{
sum+=arr[grid[i+1][j]-2];
_arr.push_back(grid[i+1][j]);
}
if(tof(grid,i,j-1) && find(_arr.begin(),_arr.end(),grid[i][j-1])==_arr.end() && grid[i][j-1]!=0)
{
sum+=arr[grid[i][j-1]-2];
_arr.push_back(grid[i][j-1]);
}
if(tof(grid,i,j+1) && find(_arr.begin(),_arr.end(),grid[i][j+1])==_arr.end() && grid[i][j+1]!=0)
{
sum+=arr[grid[i][j+1]-2];
_arr.push_back(grid[i][j+1]);
}
return sum;
}
bool tof(vector<vector<int>>& grid,int i,int j)
{
if(i<0 || i>=grid.size() || j<0 || j>=grid.size())
{
return false;
}
return true;
}
int largestIsland(vector<vector<int>>& grid) {
vector<int> arr;
int x=2;
//第一次循环是用来找寻各个陆地面积,以及将面积大小存入以其陆地数字为下标的数组
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid.size();j++)
{
if(grid[i][j]==1)
{
int sum=max_size(grid,i,j,x);
x++;
arr.push_back(sum);
}
}
}
//第二次循环是用来找寻最大面积
int max=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid.size();j++)
{
if(grid[i][j]==0)
{
int sum = judge(grid,arr,i,j);
if(sum>max)
{
max=sum;
}
}
}
}
if(arr.empty())
{
return 1;
}
if(max==0)
{
return grid.size()*grid.size();
}
return max<(grid.size()*grid.size())?max+1:max;
}
};
- 最后的判断,如果grid没有陆地,我们自己会填入一个陆地,所以要返回1。
- max==0 是有可能没有海洋格子,循环没有进去,所以max=0,实则是满陆地格子
- max<(grid.size()*grid.size())?max+1:max 这个要判断是因为我写的时候没有加入填的陆地,所以要判断一下。
优化思路
每次深搜遍历计算最大岛屿面积,做了很多重复的工作,只要用一次深搜把每个岛屿的面积记录下来就好。
- 一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积
- 在遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。
class Solution {
private:
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 >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过
dfs(grid, visited, nextx, nexty, mark);
}
}
public:
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++; // 记录下一个岛屿编号
}
}
}
if (isAllGrid) return n * m; // 如果都是陆地,返回全面积
// 以下逻辑是根据添加陆地的位置,计算周边岛屿面积之和
int result = 0; // 记录最后结果
unordered_set<int> visitedGrid; // 标记访问过的岛屿
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int 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 >= grid.size() || nearj < 0 || nearj >= grid[0].size()) continue;
if (visitedGrid.count(grid[neari][nearj])) continue; // 添加过的岛屿不要重复添加
// 把相邻四面的岛屿数量加起来
count += gridNum[grid[neari][nearj]];
visitedGrid.insert(grid[neari][nearj]); // 标记该岛屿已经添加过
}
}
result = max(result, count);
}
}
return result;
}
};
如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀