package com.freetymekiyan.algorithms.level.medium;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* 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.
* <p>
* Example 1:
* <p>
* 11110
* 11010
* 11000
* 00000
* Answer: 1
* <p>
* Example 2:
* <p>
* 11000
* 11000
* 00100
* 00011
* Answer: 3
* <p>
* Company Tags: Amazon, Microsoft, Google, Facebook, Zenefits
* Tags: Depth-first Search, Breadth-first Search, Union Find
* Similar Problems: (M) Surrounded Regions, (M) Walls and Gates, (H) Number of Islands II, (M) Number of Connected
* Components in an Undirected Graph
*/
public class NumberOfIslands {
private static final int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private NumberOfIslands n;
/**
* BFS.
* Start from the top-left corner of the grid.
* Go through each position row by row and check if it is island.
* If it is not, skip.
* If it is, BFS to find the whole region.
* Mark the region as "0" when finished.
* Number of islands increment by 1.
*/
public int numIslands(char[][] grid) {
int count = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == '0') { // Not an island
continue;
}
// bfsRecursive(grid, i, j);
bfs(grid, i, j);
count++;
}
}
return count;
}
/**
* Iterative.
* Set the starting grid to '0' to mark it as visited.
* Add it to queue to start BFS.
* Find the region and mark all grids as '0'.
*/
private void bfs(char[][] grid, int i, int j) {
Queue<int[]> queue = new ArrayDeque<>();
grid[i][j] = '0';
queue.add(new int[]{i, j});
while (!queue.isEmpty()) {
int[] p = queue.poll();
for (int[] dir : dirs) {
int row = p[0] + dir[0];
int col = p[1] + dir[1];
// Within range and not visited.
if (row >= 0 && row < grid.length && col >= 0 && col < grid[row].length
&& grid[row][col] == '1') {
grid[row][col] = '0';
queue.add(new int[]{row, col});
}
}
}
}
/**
* Recursive.
* Base case:
* If out of the grid or is not an island, return.
* If it is an island, set it to '0' as visited.
* Then recursively search the 4 adjacent neighbors.
*/
private void bfsRecursive(char[][] grid, int i, int j) {
// Out of range or not going to visit.
if (i < 0 || i >= grid.length || j < 0 || j >= grid[i].length || grid[i][j] == '0') {
return;
}
grid[i][j] = '0'; // Set to 0 can both remove this island and set to visited.
bfsRecursive(grid, i + 1, j);
bfsRecursive(grid, i - 1, j);
bfsRecursive(grid, i, j + 1);
bfsRecursive(grid, i, j - 1);
}
/**
* Union find.
* Build an Union Find data structure first.
* Then iterate all grids row by row.
*/
public int numIslandsUnionFind(char[][] grid) {
if (grid.length == 0 || grid[0].length == 0) {
return 0;
}
int m = grid.length, n = grid[0].length;
UnionFind uf = new UnionFind(m, n, grid); // Build union find.
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0') {
continue;
}
int p = i * n + j; // Id of current island. i * column + j.
int q; // Id of adjacent island.
if (i > 0 && grid[i - 1][j] == '1') {
q = p - n;
uf.union(p, q);
}
if (i < m - 1 && grid[i + 1][j] == '1') { // i is not last row.
q = p + n;
uf.union(p, q);
}
if (j > 0 && grid[i][j - 1] == '1') {
q = p - 1;
uf.union(p, q);
}
if (j < n - 1 && grid[i][j + 1] == '1') { // j is not last column.
q = p + 1;
uf.union(p, q);
}
}
}
return uf.count;
}
private char[][] buildGrid(String[] val) {
char[][] grid = new char[val.length][val[0].length()];
for (int i = 0; i < val.length; i++) {
for (int j = 0; j < val[i].length(); j++) {
grid[i][j] = val[i].charAt(j);
}
}
return grid;
}
@Before
public void setUp() {
n = new NumberOfIslands();
}
@Test
public void testExamples() {
char[][] grid = buildGrid(new String[]{"11110", "11010", "11000", "00000"});
Assert.assertEquals(1, n.numIslands(grid));
grid = buildGrid(new String[]{"11000", "11000", "00100", "00011"});
Assert.assertEquals(3, n.numIslands(grid));
}
@After
public void tearDown() {
n = null;
}
/**
* Data structure to keep track of number of connected components in the grid.
* The count is initialized as the number of island in the grid.
* Every time two islands are unified, the count will decrease by 1.
*/
private class UnionFind {
/**
* Count of connected components.
* Initialized as number of 1's.
* Whenever there is an union, decrement count by 1.
*/
public int count;
/**
* Connected component id array.
* The index is mapped from 2d array.
*/
public int[] id = null;
public UnionFind(int m, int n, char[][] grid) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
count++;
}
}
}
// Initialize id array by mapping each position in grid.
id = new int[m * n];
for (int i = 0; i < m * n; i++) {
id[i] = i;
}
}
/**
* O(n), find the root id.
* If p equals id[p], p is root.
*/
public int find(int p) {
while (p != id[p]) {
id[p] = id[id[p]]; // Cut unnecessary branches.
p = id[p]; // Move on to the next one
}
return p;
}
/**
* Check whether two points are in the same edges component.
* If connected, they will have the same id.
*/
public boolean isConnected(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
return pRoot == qRoot;
}
/**
* O(n), connect two points to the same root.
* Every time we union two points, the count will decrease by 1.
*/
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
id[pRoot] = qRoot; // Set p's root to q's
count--; // IMPORTANT!! Decrement count by 1 after union.
}
}
}