305. Number of Islands II
A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand operation which turns the water at position (row, col) into a land. Given a list of positions to operate, count the number of islands after each addLand operation. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example:
Input: m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]
Output: [1,1,2,3]
Explanation:
Initially, the 2d grid grid is filled with water. (Assume 0 represents water and 1 represents land).
0 0 0
0 0 0
0 0 0
Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land.
1 0 0
0 0 0 Number of islands = 1
0 0 0
Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land.
1 1 0
0 0 0 Number of islands = 1
0 0 0
Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land.
1 1 0
0 0 1 Number of islands = 2
0 0 0
Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land.
1 1 0
0 0 1 Number of islands = 3
0 1 0
Follow up:
Can you do it in time complexity O(k log mn), where k is the length of the positions?
方法1: Union Find
思路:
首先确定最多只会有positions.size()块陆地。将二维数组的坐标压缩成一维,并用roots记录该点的祖先。可以进行path compression 和weighted union find改进速度
易错点:
- idx = x * n + y: 不是m
- 只有当该点是海水在当地建岛才cnt++,这一步主要防止重复建岛
- 上下左右guard against oob
- 每个pos对应一个result结果
Complexity
Time complexity : O(m×n+L) where L is the number of operations, m is the number of rows and n is the number of columns. it takes O(m×n) to initialize UnionFind, and O(L) to process positions. Note that Union operation takes essentially constant time^1 when UnionFind is implemented with both path compression and union by rank.
Space complexity : O(m×n) as required by UnionFind data structure.
朴素版union find
class Solution {
public:
vector<int> numIslands2(int m, int n, vector<pair<int, int>>& positions) {
vector<int> roots(m * n, -1);
vector<int> result;
vector<vector<int>> dirs{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int cnt = 0;
for (auto pos: positions){
int id = n * pos.first + pos.second;
if (roots[id] == -1) {
roots[id] = id;
// 最多有positions.size()个岛屿
// 这里用if是因为如果在同一个地点多次建岛,不应该重复计数
// 但是如果多次建岛,下面direction的循环不会跳过,虽然肯定已经有相同的祖先,还是会四个方向的所有岛都重新assign一遍。重复进行dfs,浪费时间。
cnt ++;
}
// id遍历上下左右
for (auto dir: dirs){
int x = pos.first + dir[0];
int y = pos.second + dir[1];
int id_next = x * n + y;
// 如果邻居点超出边界或者没有岛,跳过不做处理
if (x < 0 || x >= m || y < 0 || y >= n || roots[id_next] < 0){
continue;
}
// 只有当两点都为岛屿时,union
else{
int par1 = find(roots, id_next);
int par2 = find(roots, id);
// 如果不属于同一片陆地,陆地数量--
if (par1 != par2){
roots[par1] = par2;
cnt --;
}
}
}
// 每次加入一块土地并union后,记录该时刻的cnt
result.push_back(cnt);
}
return result;
}
int find(vector<int> & roots, int id){
while (roots[id] != id){
// 这一步只往上挪了一个祖先?但是快了很多
roots[id] = roots[roots[id]];
id = roots[id];
}
return id;
}
};
用weighted union find改进。
这里union_op返回bool,标示有没有发生weighted union
class Solution{
public:
vector<int> numIslands2(int m, int n, vector<vector<int>>& positions) {
vector<int> roots(m * n, -1);
vector<int> size(m * n, 1);
vector<int> result;
vector<vector<int>> dirs{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int cnt = 0;
for (auto pos: positions){
int id = n * pos[0] + pos[1];
if (roots[id] == -1) {
roots[id] = id;
// 最多有positions.size()个岛屿
// 这里用if是因为如果在同一个地点多次建岛,不应该重复计数
cnt ++;
}
// id遍历上下左右
for (auto dir: dirs){
int x = pos[0] + dir[0];
int y = pos[1] + dir[1];
int id_next = x * n + y;
// 如果邻居点超出边界或者没有岛,跳过不做处理
if (x < 0 || x >= m || y < 0 || y >= n || roots[id_next] < 0){
continue;
}
// 只有当两点都为岛屿时,union
else{
// 如果不属于同一片陆地,陆地数量--
// 这里union_op返回bool,标示有没有发生weighted union
if (union_op(id, id_next, roots, size)){
cnt --;
}
}
}
// 每次加入一块土地并union后,记录该时刻的cnt
result.push_back(cnt);
}
return result;
}
int find_op(vector<int> & roots, int id){
while (roots[id] != id){
// 这一步只往上挪了一个祖先?
//roots[id] = roots[roots[id]];
id = roots[id];
}
return id;
}
bool union_op(int id, int idx, vector<int> & roots, vector<int> & size){
int x = find_op(roots, id);
int y = find_op(roots, idx);
if (x != y) {
if (size[x] <= size[y]){
roots[x] = roots[y]; // 这里对吗
size[y] += size[x]; // 这里对吗 对比323的模版
return true;
}
else {
roots[y] = roots[x];
size[x] += size[y];
return true;
}
}
return false;
}
};
方法2: dfs
思路:
每一次call 200题的function,每次新加入一个pos,就做一次dfs
Complexity
O(Lmn)
class Solution1 {
private:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
dfs(grid, r, c);
++num_islands;
}
}
}
return num_islands;
}
void dfs(vector<vector<char>>& grid, int r, int c) {
int nr = grid.size();
int nc = grid[0].size();
//grid[r][c] = '0'; // sink
if (r<0 || r>=nr || c<0 || c>=nc || grid[r][c] != '1') return;
grid[r][c] = '0'; // sink set as visited
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c-1);
dfs(grid, r, c+1);
}
public:
vector<int> numIslands2(int m, int n, vector<pair<int, int>>& positions) {
vector<int> ans;
vector<vector<char>> grid (m, vector<char>(n, '0'));
for (auto pos : positions) {
grid[pos.first][pos.second] = '1';
ans.push_back(numIslands(grid));
}
return ans;
}
};