Leetcode solution 200. Number of Islands (Using BFS, DFS & Disjoint Set)
Problem Statement
Given a 2d grid map of '1'
s (land) and '0'
s (water), count the number of islands. 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 1:
Input: 11110 11010 11000 00000 Output: 1
Example 2:
Input: 11000 11000 00100 00011 Output: 3
Problem link
Video Tutorial
You can find the detailed video tutorial here
Thought Process
I have recorded a video back in 2015 (5 years ago, Geez time flies) using BFS and DFS. The essential idea is every time we see a land (i.e., "1"), we would trigger a BFS or DFS search to mark all the connected grids and increase the island counts to 1.
Another solution is to use Disjoint Set (aka Union Find). There is a generic solution where we could implement a DisjointSet data structure and perform "union" steps first to assign each grid a "parent". In the 2nd iteration we can perform "find" step to count how many distinct "parents", which is the number of islands.
A quick summary of some classic use cases
- Disjoint Set / Union Find (Disjoint set is the data structure while union find is the algorithm, people normally use it interchangeably) can often be used to solve if a graph is connected or not, such as phone contact problem (who knows who, how many isolated groups are there)
- To detect if a directed graph has cycle, use Kahn's algorithm for topological sort
- To detect if a un-directed graph has cycle
Solutions
Use BFS
1 public class Block { 2 public int x; 3 public int y; 4 5 public Block(int x, int y) { 6 this.x = x; 7 this.y = y; 8 } 9 } 10 11 public int numIslandsBFS(char[][] grid) { 12 if (grid == null || grid.length == 0 || grid[0].length == 0) { 13 return 0; 14 } 15 16 int m = grid.length; 17 int n = grid[0].length; 18 19 boolean[][] visited = new boolean[m][n]; 20 int count = 0; 21 22 for (int i = 0; i < m; i++) { 23 for (int j = 0; j < n; j++) { 24 if (grid[i][j] == '0' || visited[i][j]) { 25 continue; 26 } 27 28 count++; 29 visited[i][j] = true; 30 31 Queue<Block> q = new LinkedList<>(); // queue just uses LinkedList, ArrayList doesn't implement it 32 33 // remind myself what's the diff between offer and add, 34 // some implementation it's the same, some add throws while offer return true/false if the queue is 35 // full while you still trying to add 36 q.offer(new Block(i, j)); 37 38 while (!q.isEmpty()) { 39 Block b = q.poll(); 40 41 for (int k = 0; k < 4; k++) { 42 int x = b.x + xDirection[k]; 43 int y = b.y + yDirection[k]; 44 if (this.shouldExplore(x, y, m, n, grid, visited)) { 45 visited[x][y] = true; 46 q.offer(new Block(x, y)); 47 } 48 } 49 } 50 } 51 } 52 return count; 53 } 54 55 private boolean shouldExplore(int x, int y, int row, int col, char[][] grid, boolean[][] visited) { 56 if (x >= 0 && x < row && y >= 0 && y < col && grid[x][y] == '1' && !visited[x][y]) return true; 57 58 return false; 59 }
Time Complexity: O(M*N), where M and N is row and col of grid matrix. Each grid is visited once
Space Complexity: O(M*N) since we have to track the visited grid
Use DFS
1 private final int[] xDirection = {1, 0, -1, 0}; 2 private final int[] yDirection = {0, -1, 0, 1 }; 3 4 public int numIslands(char[][] grid) { 5 if (grid == null || grid.length == 0 || grid[0].length == 0) { 6 return 0; 7 } 8 9 int m = grid.length; 10 int n = grid[0].length; 11 int islandCount = 0; 12 boolean[][] visited = new boolean[m][n]; 13 14 for (int i = 0; i < m; i++) { 15 for (int j = 0; j < n; j++) { 16 if (grid[i][j] == '0' || visited[i][j]) { 17 continue; 18 } 19 explore(i, j, m, n, grid, visited); 20 islandCount++; 21 } 22 } 23 24 return islandCount; 25 } 26 27 private void explore(int x, int y, int row, int col, char[][] grid, boolean[][] visited) { 28 if (!this.shouldExplore(x, y, row, col, grid, visited)) return; 29 30 visited[x][y] = true; 31 for (int i = 0; i < 4; i++) { 32 explore(x + this.xDirection[i], y + this.yDirection[i], row, col, grid, visited); 33 } 34 }
Time Complexity: O(M*N), where M and N is row and col of grid matrix. Each grid is visited once
Space Complexity: O(M*N) since we have to track the visited grids
Use Disjoint Set / Union Find
1 private class DisjointSet { 2 private Map<String, String> lookup = new HashMap<>(); 3 4 public void init(int row, int col) { 5 for (int i = 0; i < row; i++) { 6 for (int j = 0; j < col; j++) { 7 String key = i + "," + j; 8 // initially key value is the same, meaning the parent of the disjoint set is itself, i.e., isolated or not initialized 9 this.lookup.put(key, key); 10 } 11 } 12 } 13 14 /** 15 * @param key "row,col" is the key in the mastrix 16 * @return String, "row,col" of the parent of this key 17 */ 18 public String find(String key) throws Exception { 19 if (key == null || key.length() == 0 || !this.lookup.containsKey(key)) { 20 throw new Exception(String.format("Invalid input key %s", key)); 21 } 22 23 while (!key.equals(this.lookup.get(key))) { 24 key = this.lookup.get(key); 25 } 26 27 return key; 28 } 29 30 public void union(String key1, String key2) throws Exception { 31 if (key1 == null || key1.length() == 0 || key2 == null || key2.length() == 0) { 32 throw new Exception(String.format("key1 %s or key2 %s not valid", key1, key2)); 33 } 34 35 String parent1 = this.find(key1); 36 String parent2 = this.find(key2); 37 38 if (!parent1.equals(parent2)) { 39 this.lookup.put(parent1, parent2); 40 } 41 } 42 43 } 44 45 public int numIslandsDisjoinSet(char[][] grid) { 46 if (grid == null || grid.length == 0 || grid[0].length == 0) { 47 return 0; 48 } 49 50 int row = grid.length; 51 int col = grid[0].length; 52 // key: row,col, val: row,col 53 DisjointSet ds = new DisjointSet(); 54 ds.init(row, col); 55 Set<String> islands = new HashSet<>(); 56 57 try { 58 for (int i = 0; i < row; i++) { 59 for (int j = 0; j < col; j++) { 60 if (grid[i][j] == '1') { 61 String key = i + "," + j; 62 // union right grid 63 if (j + 1 < col && grid[i][j + 1] == '1') { 64 ds.union(key, i + "," + (j + 1)); 65 } 66 // union the below grid 67 if (i + 1 < row && grid[i + 1][j] == '1') { 68 ds.union(key, (i + 1) + "," + j); 69 } 70 } 71 } 72 } 73 74 for (int i = 0; i < row; i++) { 75 for (int j = 0; j < col; j++) { 76 if (grid[i][j] == '1') { 77 islands.add(ds.find(i + "," + j)); 78 } 79 } 80 } 81 } catch (Exception e) { 82 System.err.println(e); 83 } 84 return islands.size(); 85 }
Time Complexity: O(M*N), where M and N is row and col of grid matrix. Each grid is visited once
Space Complexity: O(M*N) since we have to track the visited grids using a map
Space Complexity: O(M*N) since we have to track the visited grids using a map
References