LeetCode 305 - Number of Islands II


http://www.cnblogs.com/jcliBlogger/p/4965051.html
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 = 3positions = [[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]
This problem requires a classic data structure called UnionFind. Take some efforts to learn it at first, like using this Princeton's notes offered by peisi.
https://discuss.leetcode.com/topic/29613/easiest-java-solution-with-explanation
This is a basic union-find problem. Given a graph with points being added, we can at least solve:
  1. How many islands in total?
  2. Which island is pointA belonging to?
  3. Are pointA and pointB connected?
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 4N operations take O(NlogN). 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. Because roots[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 & path compression. However I suggest first finish the basis, then discuss the improvements.

又是一道Union Find的经典题。这道题代码主要参考了yavinci大神。风格还是princeton Sedgewick的那一套。这里我们可以把二维的Union-Find映射为一维的Union Find。使用Quick-Union就可以完成。但这样的话Time Complexity是O(kmn)。 想要达到O(klogmn)的话可能还需要使用Weighted-Quick Union配合path compression。
Time Complexity - O(mn * k), Space Complexity - O(mn)
int[][] dirs = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

public List<Integer> numIslands2(int m, int n, int[][] positions) {
    List<Integer> result = new ArrayList<>();
    if(m <= 0 || n <= 0) return result;

    int count = 0;                      // number of islands
    int[] roots = new int[m * n];       // one island = one tree
    Arrays.fill(roots, -1);            

    for(int[] p : positions) {
        int root = n * p[0] + p[1];     // assume new point is isolated island
        roots[root] = root;             // add new island
        count++;

        for(int[] dir : dirs) {
            int x = p[0] + dir[0]; 
            int y = p[1] + dir[1];
            int nb = n * x + y;
            if(x < 0 || x >= m || y < 0 || y >= n || roots[nb] == -1) continue;
            
            int rootNb = findIsland(roots, nb);
            if(root != rootNb) {        // if neighbor is in another island
                roots[root] = rootNb;   // union two islands 
                root = rootNb;          // current tree root = joined tree root
                count--;               
            }
        }

        result.add(count);
    }
    return result;
}

public int findIsland(int[] roots, int id) {
    while(id != roots[id]) id = roots[id];
    return id;
}

PATH COMPRESSION (BONUS)


If you have time, add one line to shorten the tree. The new runtime becomes: 19ms (95.94%).
public int findIsland(int[] roots, int id) {
    while(id != roots[id]) {
        roots[id] = roots[roots[id]];   // only one line added
        id = roots[id];
    }
    return id;
}
http://www.cnblogs.com/yrbbest/p/5050749.html
加入了Path compression以及Weight, 速度快了不少。
Time Complexity - (k * logmn)  Space Complexity - O(mn),  这里k是positions的长度
    private int[] id;
    private int[] sz;
    private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> res = new ArrayList<>();
        if (positions == null || positions.length == 0 || m < 0 || n < 0) {
            return res;
        }
        id = new int[m * n]; 
        sz = new int[m * n];
        for (int i = 0; i < id.length; i++) {
            id[i] = i;
        }
        
        int count = 0;
        for (int[] position : positions) {
            int p = position[0] * n + position[1];
            sz[p]++;
            count++;
            for (int[] direction : directions) {
                int newRow = position[0] + direction[0];
                int newCol = position[1] + direction[1];
                if (newRow < 0 || newCol < 0 || newRow > m - 1 || newCol > n - 1) {
                    continue;
                }
                int q = newRow * n + newCol;
                if (sz[q] > 0) {
                    if (isConnected(p, q)) {
                        continue;    
                    } else {
                        union(p, q);
                        count--;
                    }
                }
            }
            res.add(count);
        }
        return res;
    }
    
    private int getRoot(int p) {
        while (p != id[p]) {
            id[p] = id[id[p]];
            p = id[p];
        } 
        return p;
    }
    
    private boolean isConnected(int p, int q) {
        return getRoot(p) == getRoot(q);
    }
    
    private void union(int p, int q) {
        int rootP = getRoot(p);
        int rootQ = getRoot(q);
        if (rootP == rootQ) {
            return;
        } else {
            if (sz[p] < sz[q]) {
                id[rootP] = rootQ;
                sz[q] += sz[p];
            } else {
                id[rootQ] = rootP;
                sz[p] += sz[q];
            }
        }
    }
https://discuss.leetcode.com/topic/36614/java-union-find-with-rank-beats-98-easy-to-understand-with-short-explanation
https://leetcode.com/discuss/69392/python-clear-solution-unionfind2d-weighting-compression
The following algorithm is derived from 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 inO(MN).
For our problem, If there are N positions, then there are O(N) operations and N objects then total is O(N log*N), when we don't consider the O(mn) for array initialization.
Note that log*N is almost constant (for N = 265536, log*N = 5) in this universe, so the algorithm is almost linear with N.
However, if the map is very big, then the initialization of the arrays can cost a lot of time when mnis much larger than N. In this case we should consider using a hashmap/dictionary for the underlying data structure to avoid this overhead.
Of course, we can put all the functionality into the Solution class which will make the code a lot shorter. But from a design point of view a separate class dedicated to the data sturcture is more readable and reusable.
I implemented the idea with 2D interface to better fit the problem.
private int[][] dir = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}}; public List<Integer> numIslands2(int m, int n, int[][] positions) { UnionFind2D islands = new UnionFind2D(m, n); List<Integer> ans = new ArrayList<>(); for (int[] position : positions) { int x = position[0], y = position[1]; int p = islands.add(x, y); for (int[] d : dir) { int q = islands.getID(x + d[0], y + d[1]); if (q > 0 && !islands.find(p, q)) islands.unite(p, q); } ans.add(islands.size()); } return ans; } class UnionFind2D { private int[] id; private int[] sz; private int m, n, count; public UnionFind2D(int m, int n) { this.count = 0; this.n = n; this.m = m; this.id = new int[m * n + 1]; this.sz = new int[m * n + 1]; } public int index(int x, int y) { return x * n + y + 1; } public int size() { return this.count; } public int getID(int x, int y) { if (0 <= x && x < m && 0<= y && y < n) return id[index(x, y)]; return 0; } public int add(int x, int y) { int i = index(x, y); id[i] = i; sz[i] = 1; ++count; return i; } public boolean find(int p, int q) { return root(p) == root(q); } public void unite(int p, int q) { int i = root(p), j = root(q); if (sz[i] < sz[j]) { //weighted quick union id[i] = j; sz[j] += sz[i]; } else { id[j] = i; sz[i] += sz[j]; } --count; } private int root(int i) { for (;i != id[i]; i = id[i]) id[i] = id[id[i]]; //path compression return i; }

private static final int[][] dir = {{0, 1},{1, 0},{0, -1},{-1, 0}}; public List<Integer> numIslands2(int n, int m, int[][] positions) { int[][] map = new int[n + 2][m + 2]; List<Integer> ans = new ArrayList(); int islandN = 0; UnionSet us = new UnionSet(n, m); for (int[] p : positions) { map[p[0] + 1][p[1] + 1] = 1; islandN++; for (int[] d : dir) if (map[p[0] + d[0] + 1][p[1] + d[1] + 1] > 0 && us.union(p[0], p[1], p[0] + d[0], p[1] + d[1])) islandN--; ans.add(islandN); } return ans; } private class UnionSet { int n, m; int[] p, size; public UnionSet(int a, int b) { n = a; m = b; p = new int[getID(n, m)]; size = new int[getID(n, m)]; } private int getID(int i, int j) { return i * m + j + 1; // ensure no id == 0; } private int find(int i) { if (p[i] == 0) { // == 0 means not yet initialized p[i] = i; size[i] = 1; } p[i] = (p[i] == i) ? i : find(p[i]); return p[i]; } private boolean union(int i1, int j1, int i2, int j2) { // true if combines two element of two different sets int s1 = find(getID(i1, j1)), s2 = find(getID(i2, j2)); if (s1 == s2) return false; if (size[s1] > size[s2]) { p[s2] = s1; size[s1] += size[s2]; } else { p[s1] = s2; size[s2] += size[s1]; } return true; } }

http://www.cnblogs.com/yrbbest/p/5050749.html
加入了Path compression以及Weight, 速度快了不少。
Time Complexity - (k * logmn)  Space Complexity - O(mn),  这里k是positions的长度
public class Solution {
    private int[] id;
    private int[] sz;
    private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> res = new ArrayList<>();
        if (positions == null || positions.length == 0 || m < 0 || n < 0) {
            return res;
        }
        id = new int[m * n]; 
        sz = new int[m * n];
        for (int i = 0; i < id.length; i++) {
            id[i] = i;
        }
        
        int count = 0;
        for (int[] position : positions) {
            int p = position[0] * n + position[1];
            sz[p]++;
            count++;
            for (int[] direction : directions) {
                int newRow = position[0] + direction[0];
                int newCol = position[1] + direction[1];
                if (newRow < 0 || newCol < 0 || newRow > m - 1 || newCol > n - 1) {
                    continue;
                }
                int q = newRow * n + newCol;
                if (sz[q] > 0) {
                    if (isConnected(p, q)) {
                        continue;    
                    } else {
                        union(p, q);
                        count--;
                    }
                }
            }
            res.add(count);
        }
        return res;
    }
    
    private int getRoot(int p) {
        while (p != id[p]) {
            id[p] = id[id[p]];
            p = id[p];
        } 
        return p;
    }
    
    private boolean isConnected(int p, int q) {
        return getRoot(p) == getRoot(q);
    }
    
    private void union(int p, int q) {
        int rootP = getRoot(p);
        int rootQ = getRoot(q);
        if (rootP == rootQ) {
            return;
        } else {
            if (sz[p] < sz[q]) {
                id[rootP] = rootQ;
                sz[q] += sz[p];
            } else {
                id[rootQ] = rootP;
                sz[p] += sz[q];
            }
        }
    }
}
又是一道Union Find的经典题。这道题代码主要参考了yavinci大神。风格还是princeton Sedgewick的那一套。这里我们可以把二维的Union-Find映射为一维的Union Find。使用Quick-Union就可以完成。但这样的话Time Complexity是O(kmn)。 想要达到O(klogmn)的话可能还需要使用Weighted-Quick Union配合path compression。
Time Complexity - O(mn * k), Space Complexity - O(mn)
public class Solution {
    int[][] directions = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
    
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> res = new ArrayList<>();
        if(m < 0 || n < 0 || positions == null) {
            return res;
        }
        int[] id = new int[m * n];          // union find array
        int count = 0;
        Arrays.fill(id, -1);
        
        for(int i = 0; i < positions.length; i++) {
            int index = n * positions[i][0] + positions[i][1];
            if(id[index] != -1) {
                res.add(count);
                continue;
            }
            
            id[index] = index;
            count++;
            
            for(int[] direction : directions) {
                int x = positions[i][0] + direction[0];
                int y = positions[i][1] + direction[1];
                int neighborIndex = n * x + y;
                if(x < 0 || x >= m || y < 0 || y >= n || id[neighborIndex] == -1) {
                    continue;
                }
                if(!connected(id, index, neighborIndex)) {
                    union(id, neighborIndex, index);
                    count--;    
                }
            }
            
            res.add(count);
        }
        return res;
    }
    private boolean connected(int[] id, int p, int q) {
        return id[p] == id[q];
    }
    
    private void union(int[] id, int p, int q) {
        int pid = id[p];
        int qid = id[q];
        for(int i = 0; i < id.length; i++) {
            if(id[i] == pid) {
                id[i] = qid;
            }
        }
    }
}
https://leetcode.com/discuss/69572/easiest-java-solution-with-explanations
public int findIsland(int[] roots, int id) {
    while(id != roots[id]) id = roots[id];
    return id;
}

If you have time, add one line to shorten the tree. The new runtime becomes: 19ms (95.94%).
public int findIsland(int[] roots, int id) {
    while(id != roots[id]) {
        roots[id] = roots[roots[id]];   // only one line added
        id = roots[id];
    }
    return id;
}
http://www.cnblogs.com/jcliBlogger/p/4965051.html
LIKE CODING: LeetCode [305] Number of Islands II

http://www.1point3acres.com/bbs/thread-148670-1-1.html
第一题类似于孤岛问题,follow up是如果在来个stream,每次输入一个点是岛点,问你怎么update已经有的数组并返回新的岛的个数。
Read full article from LIKE CODING: LeetCode [305] Number of Islands II

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts