题目描述
假设你设计一个游戏,用一个 m 行 n 列的 2D 网格来存储你的游戏地图。
起始的时候,每个格子的地形都被默认标记为「水」。我们可以通过使用 addLand 进行操作,将位置 (row, col) 的「水」变成「陆地」。
你将会被给定一个列表,来记录所有需要被操作的位置,然后你需要返回计算出来 每次 addLand 操作后岛屿的数量。
注意:一个岛的定义是被「水」包围的「陆地」,通过水平方向或者垂直方向上相邻的陆地连接而成。你可以假设地图网格的四边均被无边无际的「水」所包围。
请仔细阅读下方示例与解析,更加深入了解岛屿的判定。
示例:
输入: m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]
输出: [1,1,2,3]解析:
起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)
0 0 0
0 0 0
0 0 0操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。
1 0 0
0 0 0 Number of islands = 1
0 0 0操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。
1 1 0
0 0 0 岛屿的数量为 1
0 0 0操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。
1 1 0
0 0 1 岛屿的数量为 2
0 0 0操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。
1 1 0
0 0 1 岛屿的数量为 3
0 1 0拓展:
你是否能在 O(k log mn) 的时间复杂度程度内完成每次的计算?(k 表示 positions 的长度)
算法分析
方法:并查集
把二维的网格图当做一个无向图,横向或者纵向相邻的顶点之间有一条无向边,那么问题就变成了每次 addLand 操作之后在图中寻找连通分量的问题。
算法
对于每个 addLand 操作。需要注意的逻辑是:
如果 addLand 操作的顶点已经访问过,跳过;
如果 addLand 操作的顶点没有访问过,此时需要增加连通分量个数,然后再将它与「上」「下」「左」「右」合并。
代码
class Solution {
public:
vector<int> parents;
vector<int> ranks;
vector<bool> status;
int count;
void Init(int n) {
parents.resize(n);
ranks.resize(n, 1);
status.resize(n, false);
count = 0;
for(int i = 0; i < n; ++i) {
parents[i] = i;
}
}
int find(int x) {
if(x == parents[x]) {
return x;
}
parents[x] = find(parents[x]);
return parents[x];
}
void merge(int i, int j) {
int iroot = find(i);
int jroot = find(j);
if(iroot == jroot) {
return;
}
if(ranks[iroot] <= ranks[jroot]) {
parents[iroot] = jroot;
} else {
parents[jroot] = iroot;
}
if(ranks[iroot] == ranks[jroot] && iroot != jroot) {
ranks[jroot]++;
}
--count;
}
bool isConnected(int i, int j) {
return find(i) == find(j);
}
int getCount() {
return count;
}
bool inArea(int x, int y, int m, int n) {
if(x >= 0 && x < m && y >= 0 && y < n) {
return true;
}
return false;
}
vector<int> numIslands2(int m, int n, vector<vector<int>>& positions) {
int N = m * n;
Init(N);
vector<int> res;
int directions[4][2] = {{0, 1},
{1, 0},
{0, -1},
{-1, 0}};
for(int i = 0; i < positions.size(); ++i) {
int row = positions[i][0];
int col = positions[i][1];
int index = row * n + col;
if(!status[index]) {
status[index] = true;
++count;
for(auto &direction: directions) {
int newrow = row + direction[0];
int newcol = col + direction[1];
int newindex = newrow * n + newcol;
if(inArea(newrow, newcol, m, n) && status[newindex] && !isConnected(index, newindex)) {
merge(index, newindex);
}
}
}
res.emplace_back(getCount());
}
return res;
}
};
时间复杂度分析
时间复杂度: O(klog(mn)),其中 k是操作的数目, m是行数, n是列数。
空间复杂度: O(m×n),并查集所需的空间。