Graph - DSA: Number of Islands - II - Online Queries - DSU
This chapter covers solving the classic "Number of Islands II" problem using the Disjoint Set Union (DSU) data structure. Unlike static grid problems where the entire matrix is given upfront, this problem processes online queries—where island cells are added dynamically one by one, and we must report the active island count after each operation.
1. Why Do We Need DSU for Online Queries?
- Status Quo in Static Islands: In standard static island problems, the entire grid is known beforehand, and we use standard Traversals (BFS/DFS) or a visited matrix to count components.
- The Dynamic Nature of Online Queries: Here, the grid starts completely empty (all sea). Queries arrive sequentially: "Build an island at coordinate ."
- The Challenge: If we ran a full BFS/DFS traversal from scratch for every single query, the time complexity would skyrocket (), leading to Time Limit Exceeded (TLE) in an interview or online judge.
- The Solution: We need an algorithm capable of handling dynamic graph connectivity efficiently. Disjoint Set Union (DSU) allows us to merge components in nearly constant time (), making it the ideal choice for incremental graph construction.
2. Core Mental Model: The Island Merge Game
- Real-Life Analogy: Imagine building an archipelago on an empty ocean.
- When you drop land on an empty coordinate , it initially stands alone as an individual island, increasing the total island count by .
- However, if that newly dropped cell shares a horizontal or vertical side with an existing neighboring island, it acts as a bridge.
- If the neighbor belongs to a different component, dropping this cell connects two previously separate islands into one, decreasing the total island count by .
- If the neighbor belongs to the same component, no merger happens, and the count remains unchanged.
3. Cell-to-Node Flattening Formula
Disjoint Set data structures operate on 1D linear node identifiers (from to ), whereas our grid is 2D with dimensions rows and columns. We must convert any 2D coordinate into a unique 1D node ID.
The Conversion Formula
For a grid with columns, every row consumes exactly node indices. Therefore, the formula to convert a 2D coordinate to a 1D node index is:
Example Walkthrough ():
- Cell
- Cell
- Cell
Row 0: [ 0, 1, 2, 3, 4] (Consumes indices 0 to 4)
Row 1: [ 5, 6, 7, 8, 9] (Consumes indices 5 to 9)
Row 2: [10, 11, 12, 13, 14] (Consumes indices 10 to 14)
Row 3: [15, 16, 17, 18, 19] (Consumes indices 15 to 19)
4. Step-by-Step Algorithm Workflow
When a query query is processed:
- Duplicate Check: Check if coordinate was already visited using a
visitedgrid. If it was already an island, appending the current count again and moving on preserves state without redundant work. - Mark & Increment: Mark as visited in our tracking matrix and provisionally increment the island
countby . - Explore 4-Directional Neighbors: Check all valid adjacent cells (Up, Right, Down, Left) using Delta arrays ().
- Connectivity Check & Union:
- Verify if the adjacent cell is valid and already marked as an island (
visited == true). - Find the ultimate parents of the current node and the neighbor using DSU (
findUPar). - If their ultimate parents are different, they belong to separate components. Perform
unionBySizeand decrement the islandcountby .
- Verify if the adjacent cell is valid and already marked as an island (
- Record Result: Store the current island count in the answer list.
5. Direction Vectors and Boundary Validation
To check all four neighbors cleanly without writing repetitive code, we use directional delta arrays:
int dr[] = {-1, 0, 1, 0};
int dc[] = {0, 1, 0, -1};
For any current cell , the four neighbors are computed as:
- Up:
row - 1, col(dr = -1, dc = 0) - Right:
row, col + 1(dr = 0, dc = 1) - Down:
row + 1, col(dr = 1, dc = 0) - Left:
row, col - 1(dr = 0, dc = -1)
Boundary Validation Helper
A neighbor cell is valid if and only if it falls within the grid boundaries:
6. Complete Solution Implementation (Java & C++)
Java Implementation Structure
class DisjointSet {
List<Integer> parent = new ArrayList<>();
List<Integer> size = new ArrayList<>();
public DisjointSet(int n) {
for (int i = 0; i <= n; i++) {
parent.add(i);
size.add(1);
}
}
public int findUPar(int node) {
if (node == parent.get(node)) {
return node;
}
int ulp = findUPar(parent.get(node));
parent.set(node, ulp);
return parent.get(node);
}
public void unionBySize(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (size.get(ulp_u) < size.get(ulp_v)) {
parent.set(ulp_u, ulp_v);
size.set(ulp_v, size.get(ulp_v) + size.get(ulp_u));
} else {
parent.set(ulp_v, ulp_u);
size.set(ulp_u, size.get(ulp_u) + size.get(ulp_v));
}
}
}
class Solution {
private boolean isValid(int adjR, int adjC, int n, int m) {
return adjR >= 0 && adjR < n && adjC >= 0 && adjC < m;
}
public List<Integer> numOfIslands(int n, int m, int[][] operators) {
DisjointSet ds = new DisjointSet(n * m);
int[][] vis = new int[n][m];
int cnt = 0;
List<Integer> ans = new ArrayList<>();
int[] dr = {-1, 0, 1, 0};
int[] dc = {0, 1, 0, -1};
for (int[] it : operators) {
int row = it[0];
int col = it[1];
if (vis[row][col] == 1) {
ans.add(cnt);
continue;
}
vis[row][col] = 1;
cnt++;
for (int i = 0; i < 4; i++) {
int adjR = row + dr[i];
int adjC = col + dc[i];
if (isValid(adjR, adjC, n, m)) {
if (vis[adjR][adjC] == 1) {
int nodeNo = row * m + col;
int adjNodeNo = adjR * m + adjC;
if (ds.findUPar(nodeNo) != ds.findUPar(adjNodeNo)) {
cnt--;
ds.unionBySize(nodeNo, adjNodeNo);
}
}
}
}
ans.add(cnt);
}
return ans;
}
}
7. Complexity Analysis
8. Summary and Key Takeaways
- Online Queries: Problems where inputs arrive dynamically and require immediate incremental answers are best solved using structures that support fast dynamic connectivity.
- DSU Utility: Disjoint Set Union shines in dynamic graphs where edges/connections are added incrementally over time.
- 2D-to-1D Mapping: Flatten grid coordinates into a unique linear node ID using the formula .
- Component Counting Trick: Increment the island count by upon encountering a fresh land cell, and decrement by every time a successful DSU merge bridges two previously disconnected components.
- Idempotency via Visited Checks: Guard against redundant operations on already visited cells to ensure linear efficiency and correct component tracking.
- Interview Standout Quality: Encapsulating DSU logic inside a clean, dedicated modular class rather than cluttering the solution function demonstrates robust software engineering principles.