题目来源
题目描述
假设你设计一个游戏,用一个 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 代表「陆地」)
- 操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。此时存在 2 个岛屿。
- 操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。此时存在 3 个岛屿。
题目解析
并查集
和其他的不同是,这里的并查集是动态生成的:填海造陆
实现一
class Solution {
class UnionFind{
private:
std::vector<int> parent;
std::vector<int> size;
std::vector<int> help;
const int row, col;
int cnt;
public:
UnionFind(int m, int n) : row(m), col(n), cnt(0){
parent.resize(row * col);
size.resize(row * col);
help.resize(row * col);
}
int connect(int r, int c){
// 计算出(r, c)对应的坐标
int i = index(r, c);
if(size[i] == 0){ // 如果这不是海洋,那么可以填
// 填海造陆
parent[i] = i; // 填海
size[i] = 1; // 填海
++cnt; // 填海
// 将新造的陆地 与四周的陆地打通(如果能够打通的就打通)
merge(r - 1, c, r, c);
merge(r + 1, c, r, c);
merge(r, c - 1, r, c);
merge(r, c + 1, r, c);
}
return cnt;
}
private:
void merge(int r1, int c1, int r2, int c2){
// 超出边界了,什么也不干
if(r1 < 0 || r1 >= row || r2 < 0 || r2 >= row
|| c1 < 0 || c1 >= col || c2 < 0 || c2 >= col){
return ;
}
// 计算对应的索引,如果发现某一个索引位置是海洋,那么就不需要联通
int i1 = index(r1, c1), i2 = index(r2, c2);
if(size[i1] == 0 || size[i2] == 0){
return;
}
// 找代表节点
int f1 = findRoot(i1), f2 = findRoot(i2);
// 如果它们属于同一个领主,那么直接返回
if(f1 == f2){
return;
}
//大的领主打败小的领主
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
} else {
size[f2] += size[f1];
parent[f1] = f2;
}
cnt--;
}
int index(int i, int j) const{
return i * col + j;
}
int findRoot(int i){
int hi = 0;
while (i != parent[i]){
help[hi++] = i;
i = parent[i];
}
for ( hi--; hi >= 0; --hi) {
parent[help[hi]] = i;
}
return i;
}
};
public:
std::vector<int> numIslands(int m, int n, std::vector<std::vector<int>> positions) {
UnionFind unionFind(m, n);
std::vector<int> ans;
for(auto position : positions){
int cnt = unionFind.connect(position[0], position[1]);
ans.emplace_back(cnt);
}
return ans;
}
};
实现二
如果m*n比较大,会经历很重的初始化,怎么优化呢?
注意,下面的可能会超时
class Solution {
class UnionFind{
private:
std::unordered_map<std::string, std::string> parent;
std::unordered_map<std::string, int> size;
std::vector<std::string> help;
int cnt;
public:
explicit UnionFind() : cnt(0){
}
bool connect(int r, int c){
std::string key = std::to_string(r) + "_" + std::to_string(c);
if(parent.count(key) == 0){
parent[key] = key;
size[key] = 1;
++cnt;
std::string up = std::to_string(r - 1) + "_" + std::to_string(c);
std::string down = std::to_string(r + 1) + "_" + std::to_string(c);
std::string left = std::to_string(r) + "_" + std::to_string(c - 1);
std::string right = std::to_string(r) + "_" + std::to_string(c + 1);
merge(key, up);
merge(key, down);
merge(key, left);
merge(key, right);
}
return cnt;
}
private:
std::string findRoot(std::string cur){
while (cur != parent[cur]){
help.emplace_back(cur);
cur = parent[cur];
}
for(auto &str : help){
parent[str] = cur;
}
return cur;
}
void merge(const std::string& s1, const std::string& s2){
// 如果发现某一个位置是海洋,那么就不需要联通
if(parent.count(s1) || parent.count(s2)){
return;
}
std::string f1 = findRoot(s1), f2 = findRoot(s2);
if(f1 == f2){
return;
}
int size1 = size[f1], size2 = size[f2];
std::string big = size1 >= size2 ? f1 : f2;
std::string small = big == f1 ? f2 : f1;
parent[small] = big;
size[big] += size[small];
--cnt;
}
};
public:
std::vector<int> numIslands(int m, int n, std::vector<std::vector<int>> positions) {
UnionFind unionFind;
std::vector<int> ans;
for(auto position : positions){
int cnt = unionFind.connect(position[0], position[1]);
ans.emplace_back(cnt);
}
return ans;
}
};