每日算法系列【LeetCode 827】最大人工岛
题目描述
在二维地图上, 0 代表海洋, 1 代表陆地,我们最多只能将一格 0 海洋变成 1 变成陆地。
进行填海之后,地图上最大的岛屿面积是多少?(上、下、左、右四个方向相连的 1 可形成岛屿)
示例1
输入:
[[1, 0], [0, 1]]
输出:
3
解释:
将一格 0 变成 1 ,最终连通两个小岛得到面积为 3 的岛屿。
示例2
输入:
[[1, 1], [1, 0]]
输出:
4
解释:
将一格 0 变成 1 ,岛屿的面积扩大为 4 。
示例3
输入:
[[1, 1], [1, 1]]
输出:
4
解释:
没有 0 可以让我们变成 1 ,面积依然为 4 。
提示
- 1 <= grid.length = grid[0].length <= 50
- 0 <= grid[i][j] <= 1
题解
没有做过这种类型题目的强烈建议自己动手实现一遍,对提升代码能力有很大帮助。
这题虽然是 hard 难度,但其实没有什么算法,只用到了 dfs 求连通块而已。我们只需要知道填充了一个 0 之后,它上下左右跟它相邻的 4 块连通块是多大。
首先普及一下什么是连通块,就是一块由 1 组成的区域,其中任意两块 1 都可以由一条 1 组成的路径走过去(只能上下左右走)。那么这题我们遍历所有的 0 ,把它四周的连通块大小加起来就是填充了这块 0 之后的连通块大小了。那么问题是怎么知道它周围连通块的大小呢?
首先我们得求出所有连通块的大小,然后存下来,这就得用到 dfs 了。
用二维数组 index (初始化为 -1)记录方块是否被搜索过。然后遍历所有的方块,如果是 1 ,并且 index 为 -1 (也就是没被搜索过),那么它的 index 设为 1 ,表示被搜索过了。然后从它开始向四周进行 dfs ,直到跟它相连的所有 1 的连通块都被搜索完毕。这时候整块连通块里面的 1 方块的 index 都被设成了 1 ,就算以后被遍历到了也不会被搜索了。那么大小怎么记录呢?只需要设置一个变量 cnt 初始为 1 ,然后 dfs 的时候遇到一个未被搜索的块就加 1 ,最后 cnt 就是当前连通块的大小了。
光知道了每个连通块的大小还不行,对于一个 0 块,四周的 1 方块万一属于同一个连通块怎么办?得区分它们,不然就会被重复计算。所以在上面的 dfs 之前,给那块连通块一个唯一的编号(从 0 开始),遇到下一个连通块就加 1 。这样 index 数组就可以用来存连通块的编号了,同时还能表示方块是否被搜索过。
具体的细节还得看代码,有很多实现的技巧,当然我写的还有一些优化余地。
代码
c++
class Solution {
public:
static const int N = 55;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int index[N][N], area[N*N], flag[N*N];
int m, n;
int largestIsland(vector<vector<int>>& grid) {
m = grid.size();
n = grid[0].size();
memset(index, -1, sizeof index);
memset(area, 0, sizeof area);
memset(flag, 0, sizeof flag);
int res = 0, idx = -1;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1 && index[i][j] == -1) {
int cnt = 1;
index[i][j] = ++idx;
dfs(i, j, idx, cnt, grid);
area[idx] = cnt;
res = max(res, cnt);
}
}
}
for (int x = 0; x < m; ++x) {
for (int y = 0; y < n; ++y) {
if (grid[x][y] == 0) {
int tmp = 1;
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (inside(nx, ny) && grid[nx][ny] == 1 && !flag[index[nx][ny]]) {
flag[index[nx][ny]] = 1;
tmp += area[index[nx][ny]];
}
}
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (inside(nx, ny) && grid[nx][ny] == 1 && flag[index[nx][ny]]) {
flag[index[nx][ny]] = 0;
}
}
res = max(res, tmp);
}
}
}
return res;
}
bool inside(int x, int y) {
return 0 <= x && x < m && 0 <= y && y < n;
}
void dfs(int x, int y, int idx, int& cnt, vector<vector<int>>& grid) {
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (inside(nx, ny) && grid[nx][ny] == 1 && index[nx][ny] == -1) {
index[nx][ny] = idx;
dfs(nx, ny, idx, ++cnt, grid);
}
}
}
};
后记
dfs 求连通块是常规操作,必须要学会,很考验代码功底,要细心,不然很容易写错。
公众号「算法码上来」。godweiyang带你学习算法,不管是编程算法,还是深度学习、自然语言处理算法都一网打尽,更有各种计算机新鲜知识和你分享。别急,算法码上来。