Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

305. Number of Islands II - Leetcode Solution

Problem Description

The Number of Islands II problem on Leetcode asks you to dynamically track the number of islands in a 2D grid as land is added. You are given the grid's dimensions, m rows and n columns, and a list of positions where each position is a coordinate (row, col) where land is added (from water). Initially, the grid is all water (represented by 0). Each time you add land (1), you must report the current number of islands.

  • An island is a group of 1s (land) connected vertically or horizontally.
  • After each addition, return the number of islands so far.
  • Land can be added multiple times to the same position; ignore duplicate additions.

Constraints:

  • 1 ≤ m, n ≤ 10000
  • 1 ≤ positions.length ≤ 2000
  • 0 ≤ row < m, 0 ≤ col < n

Thought Process

The naive approach would be to rebuild the entire grid and recount islands after each addition, using DFS or BFS for connected components. However, this is inefficient as the grid can be very large and there can be many additions.

We need a way to efficiently merge and track connected components as new land is added. The Union-Find (Disjoint Set Union) data structure is ideal for this, as it can quickly join sets (merge islands) and check if two lands are in the same island.

The key insight is: each new land can either create a new island or connect to existing neighboring islands, possibly merging them into one. So, we should:

  • Keep track of each land cell's "parent" (the root of its island).
  • When adding a new land, check its four neighbors. If any neighbor is land, union the islands.
  • Efficiently count the number of unique islands after each addition.

Solution Approach

We'll use a Union-Find (Disjoint Set Union) structure with path compression for efficiency. Here's the step-by-step approach:

  1. Initialize:
    • Use a dictionary or array to store the parent of each land cell (using a 1D index for 2D coordinates).
    • Keep a count of the current number of islands.
  2. For each position in positions:
    • Convert (row, col) to a unique id: row * n + col.
    • If this cell is already land, skip it (ignore duplicates).
    • Set its parent to itself and increment the island count by 1 (assuming it's a new island).
    • For each of the four neighbors (up, down, left, right):
      • If the neighbor is land, find its root parent.
      • If the neighbor's root is different from the current cell's root, union them (merge islands), and decrement the island count by 1.
    • After checking all neighbors, append the current island count to the result list.
  3. Return the result list after processing all positions.

Why Union-Find? Union-Find allows us to efficiently merge and query connected components (islands) in nearly constant time, making it suitable for dynamic connectivity problems like this.

Example Walkthrough

Let's walk through an example with m = 3, n = 3, and positions = [[0,0],[0,1],[1,2],[2,1],[1,1]].

  1. Add (0,0): New land, new island. Islands = 1.
  2. Add (0,1): New land, check left neighbor (0,0) is land, so merge. Islands = 1 (merged).
  3. Add (1,2): New land, no land neighbors. Islands = 2.
  4. Add (2,1): New land, no land neighbors. Islands = 3.
  5. Add (1,1): New land, neighbors: (0,1), (1,2), (2,1) are all land, so merge all into one island. Islands = 1.

The result is [1,1,2,3,1].

Time and Space Complexity

Brute-force Approach:

  • For each addition, BFS/DFS to count islands: O(mn) per addition, O(kmn) total for k positions.
  • Space: O(mn) for the grid.
Optimized Union-Find Approach:
  • Each Union-Find operation is nearly O(1) (amortized, with path compression and union by rank).
  • For k additions, total time is O(k * α(k)), where α is the inverse Ackermann function (very slow-growing, almost constant).
  • Space: O(mn) in the worst case (if all cells become land), but typically O(k) for only added land cells.

Summary

The Number of Islands II problem is efficiently solved by dynamically tracking connected components using the Union-Find data structure. By representing each land cell as a node and merging connected neighbors, we can quickly update and report the number of islands after each land addition. This approach is highly efficient and scales well even for large grids, thanks to the nearly constant-time operations of Union-Find with path compression.

Code Implementation

class Solution:
    def numIslands2(self, m, n, positions):
        parent = {}
        res = []
        count = 0

        def find(x):
            if parent[x] != x:
                parent[x] = find(parent[x])
            return parent[x]

        def union(x, y):
            rootx = find(x)
            rooty = find(y)
            if rootx != rooty:
                parent[rootx] = rooty
                return True
            return False

        directions = [(0,1),(1,0),(-1,0),(0,-1)]
        for r, c in positions:
            idx = r * n + c
            if idx in parent:
                res.append(count)
                continue
            parent[idx] = idx
            count += 1
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                nidx = nr * n + nc
                if 0 <= nr < m and 0 <= nc < n and nidx in parent:
                    if union(idx, nidx):
                        count -= 1
            res.append(count)
        return res
      
class Solution {
public:
    vector<int> numIslands2(int m, int n, vector<vector<int>>& positions) {
        vector<int> res;
        unordered_map<int, int> parent;
        int count = 0;
        vector<pair<int, int>> dirs{{0,1},{1,0},{-1,0},{0,-1}};
        
        function<int(int)> find = [&](int x) {
            if (parent[x] != x)
                parent[x] = find(parent[x]);
            return parent[x];
        };
        
        auto union_set = [&](int x, int y) {
            int rx = find(x);
            int ry = find(y);
            if (rx != ry) {
                parent[rx] = ry;
                return true;
            }
            return false;
        };
        
        for (auto& pos : positions) {
            int r = pos[0], c = pos[1];
            int idx = r * n + c;
            if (parent.count(idx)) {
                res.push_back(count);
                continue;
            }
            parent[idx] = idx;
            count++;
            for (auto& d : dirs) {
                int nr = r + d.first, nc = c + d.second;
                int nidx = nr * n + nc;
                if (nr >= 0 && nr < m && nc >= 0 && nc < n && parent.count(nidx)) {
                    if (union_set(idx, nidx))
                        count--;
                }
            }
            res.push_back(count);
        }
        return res;
    }
};
      
class Solution {
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> res = new ArrayList<>();
        Map<Integer, Integer> parent = new HashMap<>();
        int count = 0;
        int[][] dirs = {{0,1},{1,0},{-1,0},{0,-1}};
        
        Function<Integer, Integer> find = new Function<Integer, Integer>() {
            public Integer apply(Integer x) {
                if (!parent.get(x).equals(x))
                    parent.put(x, this.apply(parent.get(x)));
                return parent.get(x);
            }
        };
        
        for (int[] pos : positions) {
            int r = pos[0], c = pos[1];
            int idx = r * n + c;
            if (parent.containsKey(idx)) {
                res.add(count);
                continue;
            }
            parent.put(idx, idx);
            count++;
            for (int[] d : dirs) {
                int nr = r + d[0], nc = c + d[1];
                int nidx = nr * n + nc;
                if (nr >= 0 && nr < m && nc >= 0 && nc < n && parent.containsKey(nidx)) {
                    int rootIdx = find.apply(idx);
                    int rootNidx = find.apply(nidx);
                    if (rootIdx != rootNidx) {
                        parent.put(rootIdx, rootNidx);
                        count--;
                    }
                }
            }
            res.add(count);
        }
        return res;
    }
}
      
var numIslands2 = function(m, n, positions) {
    const parent = {};
    const res = [];
    let count = 0;
    const dirs = [[0,1],[1,0],[-1,0],[0,-1]];
    
    function find(x) {
        if (parent[x] !== x)
            parent[x] = find(parent[x]);
        return parent[x];
    }
    
    function union(x, y) {
        let rootx = find(x), rooty = find(y);
        if (rootx !== rooty) {
            parent[rootx] = rooty;
            return true;
        }
        return false;
    }
    
    for (const [r, c] of positions) {
        const idx = r * n + c;
        if (parent.hasOwnProperty(idx)) {
            res.push(count);
            continue;
        }
        parent[idx] = idx;
        count++;
        for (const [dr, dc] of dirs) {
            const nr = r + dr, nc = c + dc;
            const nidx = nr * n + nc;
            if (nr >= 0 && nr < m && nc >= 0 && nc < n && parent.hasOwnProperty(nidx)) {
                if (union(idx, nidx)) count--;
            }
        }
        res.push(count);
    }
    return res;
};