Leetcode: Number of Islands II && Summary of Union Find

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:

Given m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]].
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
We return the result as an array: [1, 1, 2, 3]

Challenge:

Can you do it in time complexity O(k log mn), where k is the length of the positions?

Union Find

 Princeton's lecture note on Union Find in Algorithms and Data Structures It is a well organized note with clear illustration describing from the naive QuickFind to the one with Weighting and Path compression. With Weighting and Path compression, The algorithm runs in 

O((M+N) log* N) where M is the number of operations ( unite and find ), N is the number of objects, log* is iterated logarithm while the naive runs in O(MN).

 

方法一: Union Find based on Quick Find

我觉得:Union复杂度: O(M*N), where M is the number of calls of Union, and N is the size of id array, in our case N=m*n

Find复杂度: O(1)

实际运行时间199ms

 1 public class Solution {
 2     public List<Integer> numIslands2(int m, int n, int[][] positions) {
 3         int[][] dirs = new int[][]{{-1,0},{1,0},{0,1},{0,-1}};
 4         unionFind uf = new unionFind(m*n);
 5         List<Integer> res = new ArrayList<Integer>();
 6         for (int[] pos : positions) {
 7             int cur = pos[0]*n + pos[1];
 8             uf.ids[cur] = cur;
 9             uf.count++;
10             for (int[] dir : dirs) {
11                 int x = dir[0] + pos[0];
12                 int y = dir[1] + pos[1];
13                 int nb = x*n+y;
14                 if (x<0 || x>=m || y<0 || y>=n || uf.ids[nb]==-1) continue;
15                 if (uf.find(nb) != uf.find(cur)) {
16                     uf.union(nb, cur);
17                 }
18             }
19             res.add(uf.count);
20         }
21         return res;
22     }
23     
24         public class unionFind {
25             int[] ids;
26             int count;
27             public unionFind(int num) {
28                 this.ids = new int[num];
29                 Arrays.fill(ids, -1);
30                 this.count = 0;
31             }
32             public int find(int num) {
33                 return ids[num];
34             }
35             public boolean union(int n1, int n2) {
36                 int id1=ids[n1], id2=ids[n2];
37                 if (id1 != id2) {
38                     for (int i=0; i<ids.length; i++) {
39                         if (ids[i] == id2) {
40                             ids[i] = id1;
41                         }
42                     }
43                     count--;
44                     return true;
45                 }
46                 return false;
47             }
48         }
49 }

 

Faster Union Find方法2:Union Find Based on Quick Union 参考:https://leetcode.com/discuss/69572/easiest-java-solution-with-explanations

Quick Union is Faster than Quick Find

The idea is simple. To represent a list of islands, we use trees. i.e., a list of roots. This helps us find the identifier of an island faster. If roots[c] = p means the parent of node c is p, we can climb up the parent chain to find out the identifier of an island, i.e., which island this point belongs to:

Do root[root[roots[c]]]... until root[c] == c;

To transform the two dimension problem into the classic UF, perform a linear mapping:

int id = n * x + y;

Initially assume every cell are in non-island set {-1}. When point A is added, we create a new root, i.e., a new island. Then, check if any of its 4 neighbors belong to the same island. If not,union the neighbor by setting the root to be the same. Remember to skip non-island cells.

UNION operation is only changing the root parent so the running time is O(1)

FIND operation is proportional to the depth of the tree. If N is the number of points added, the average running time is O(logN), and a sequence of 4M operations take O(MlogN). If there is no balancing, the worse case could be O(N^2).

Remember that one island could have different roots[node] value for each node. Becauseroots[node] is the parent of the node, not the highest root of the island. To find the actually root, we have to climb up the tree by calling findIsland function.

Here I've attached my solution. There can be at least two improvements: union by rank & pass compression. However I suggest first finish the basis, then discuss the improvements.

Cheers!

time: O(Mlog(N)), space: O(N)
M表示增加land的数量,N表示矩阵中点的个数即m*n。

实际运行28ms

 1 public class Solution {
 2     public List<Integer> numIslands2(int m, int n, int[][] positions) {
 3         int[][] dirs = new int[][]{{-1,0},{1,0},{0,1},{0,-1}};
 4         unionFind uf = new unionFind(m*n);
 5         List<Integer> res = new ArrayList<Integer>();
 6         for (int[] pos : positions) {
 7             int cur = pos[0]*n + pos[1];
 8             uf.ids[cur] = cur;
 9             uf.count++;
10             for (int[] dir : dirs) {
11                 int x = dir[0] + pos[0];
12                 int y = dir[1] + pos[1];
13                 int nb = x*n+y;
14                 if (x<0 || x>=m || y<0 || y>=n || uf.ids[nb]==-1) continue;
15                 int rootNb = uf.root(nb);
16                 int rootCur = uf.root(cur);
17                 if (rootCur != rootNb) { //not connect
18                     uf.union(rootCur, rootNb);
19                     uf.count--;
20                 }
21             }
22             res.add(uf.count);
23         }
24         return res;
25     }
26     
27         public class unionFind { //ids[]记录上一跳pos,root记录最上面的pos,union(i, j)修改i的root的上一跳为j的root
28             int[] ids;
29             int count;
30             public unionFind(int num) {
31                 this.ids = new int[num];
32                 Arrays.fill(ids, -1);
33                 this.count = 0;
34             }
35             
36             public int root(int i) { //FIND operation is proportional to the depth of the tree.the average running time is O(logN)
37                 while (ids[i] != i) i = ids[i];
38                 return i;
39             }
40             
41             public boolean isConnected(int i, int j) {
42                 return root(i) == root(j);
43             }
44             
45             public void union(int i, int j) {
46                 int iRoot = root(i);
47                 int jRoot = root(j);
48                 ids[iRoot] = jRoot;
49             }
50         }
51 }

 

 

Summary of Union Find:

 Princeton's lecture note on Union Find

Quick Find

 

 

Quick Union

Here is a very easy understanding video by Stanford(看3:00开始的例子,非常简单, 一看就懂)

Compare of Fast Find & Fast Union, though worst case time complexity is almost the same, fast union is faster than fast find

转载于:https://www.cnblogs.com/EdwardLiu/p/5087633.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值