200. Number of Islands - Explanation

Problem Link

Description

Given a 2D grid grid where '1' represents land and '0' represents water, count and return the number of islands.

An island is formed by connecting adjacent lands horizontally or vertically and is surrounded by water. You may assume water is surrounding the grid (i.e., all the edges are water).

Example 1:

Input: grid = [
    ["0","1","1","1","0"],
    ["0","1","0","1","0"],
    ["1","1","0","0","0"],
    ["0","0","0","0","0"]
  ]
Output: 1

Example 2:

Input: grid = [
    ["1","1","0","0","1"],
    ["1","1","0","0","1"],
    ["0","0","1","0","0"],
    ["0","0","0","1","1"]
  ]
Output: 4

Constraints:

  • 1 <= grid.length, grid[i].length <= 100
  • grid[i][j] is '0' or '1'.


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(m * n) time and O(m * n) space, where m is the number of rows and n is the number of columns in the grid.


Hint 1

An island is a group of 1's in which every 1 is reachable from any other 1 in that group. Can you think of an algorithm that can find the number of groups by visiting a group only once? Maybe there is a recursive way of doing it.


Hint 2

We can use the Depth First Search (DFS) algorithm to traverse each group independently. We iterate through each cell of the grid. When we encounter a 1, we perform a DFS starting at that cell and recursively visit every other 1 that is reachable. During this process, we mark the visited 1's as 0 to ensure we don't revisit them, as they belong to the same group. The number of groups corresponds to the number of islands.


Company Tags


Prerequisites

Before attempting this problem, you should be comfortable with:

  • 2D Arrays/Matrices - Navigating and manipulating grid-based data structures
  • Depth First Search (DFS) - Recursive traversal to explore all connected components
  • Breadth First Search (BFS) - Level-by-level traversal using a queue
  • Union-Find (Disjoint Set Union) - Data structure for efficiently tracking connected components

Intuition

Think of the grid as a map where '1' is land and '0' is water.
An island is a group of connected land cells (up, down, left, right).
Whenever we find a land cell that hasn’t been visited, we start a DFS to sink the entire island by marking all its connected land as water. Each DFS call corresponds to one island.

Algorithm

  1. Iterate through every cell in the grid.
  2. When a cell with value '1' is found:
    • Increment the island count.
    • Run DFS from that cell.
  3. In DFS:
    • If the cell is out of bounds or is '0', return.
    • Mark the current cell as '0' (visited).
    • Recursively explore all 4 directions (up, down, left, right).
  4. Continue until all cells are processed.
  5. Return the total island count.
Example - Dry Run

Input Grid:

grid = [
  ["1","1","0","0"],
  ["1","1","0","0"],
  ["0","0","1","0"],
  ["0","0","0","1"]
]

Visual Representation:

Input Grid (1 = land, 0 = water):

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

Legend: █ = land ('1'), ░ = water ('0'), ✓ = visited

Step-by-Step Walkthrough:


Step 1: Found land at (0,0), start DFS
        Island count: 0 → 1

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ █ │ ░ │ ░ │  ← Starting DFS here
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 2: DFS explores neighbors of (0,0)
        Visit (1,0) - it's land, mark visited

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ █ │ ░ │ ░ │  ← Visited from (0,0)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 3: DFS explores neighbors of (1,0)
        Visit (1,1) - it's land, mark visited

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │  ← Visited from (1,0)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 4: DFS explores neighbors of (1,1)
        Visit (0,1) - it's land, mark visited
        All other neighbors are water or visited

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │  ← Visited from (1,1)
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        DFS complete for first island.
        Island count: 1



Step 5: Continue scanning, find land at (2,2)
        Start DFS, island count: 1 → 2

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │  ← New island found!
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        No land neighbors, DFS complete.
        Island count: 2



Step 6: Continue scanning, find land at (3,3)
        Start DFS, island count: 2 → 3

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ ✓ │  ← New island found!
  └───┴───┴───┴───┘

        No land neighbors, DFS complete.
        Island count: 3

Final Result: 3 islands


class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
        ROWS, COLS = len(grid), len(grid[0])
        islands = 0

        def dfs(r, c):
            if (r < 0 or c < 0 or r >= ROWS or
                c >= COLS or grid[r][c] == "0"
            ):
                return

            grid[r][c] = "0"
            for dr, dc in directions:
                dfs(r + dr, c + dc)

        for r in range(ROWS):
            for c in range(COLS):
                if grid[r][c] == "1":
                    dfs(r, c)
                    islands += 1

        return islands

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns in the gridgrid.


Intuition

Treat the grid like a map where '1' represents land and '0' represents water.
Each island is a group of connected land cells.
When we encounter a land cell, we use BFS to visit all connected land cells and mark them as water, ensuring the same island is not counted again.

Algorithm

  1. Traverse every cell in the grid.
  2. When a '1' (land) cell is found:
    • Increment the island count.
    • Start BFS from that cell.
  3. In BFS:
    • Push the starting cell into a queue and mark it as '0'.
    • While the queue is not empty:
      • Pop a cell.
      • Explore its 4 neighbors (up, down, left, right).
      • If a neighbor is land, mark it as '0' and add it to the queue.
  4. Continue scanning the grid.
  5. Return the total number of islands.
Example - Dry Run

Input Grid:

grid = [
  ["1","1","0","0"],
  ["1","1","0","0"],
  ["0","0","1","0"],
  ["0","0","0","1"]
]

Visual Representation:

Input Grid (1 = land, 0 = water):

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

Legend: █ = land ('1'), ░ = water ('0'), ✓ = visited

Step-by-Step Walkthrough:


Step 1: Found land at (0,0), start BFS
        Add (0,0) to queue, mark visited
        Island count: 0 → 1
        Queue: [(0,0)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ █ │ ░ │ ░ │  ← Starting BFS here
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 2: Dequeue (0,0), check neighbors
        (0,1) is land → add to queue, mark visited
        (1,0) is land → add to queue, mark visited
        Queue: [(0,1), (1,0)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │  ← Added (0,1)
  ├───┼───┼───┼───┤
1 │ ✓ │ █ │ ░ │ ░ │  ← Added (1,0)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 3: Dequeue (0,1), check neighbors
        (0,0) already visited
        (1,1) is land → add to queue, mark visited
        Queue: [(1,0), (1,1)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │  ← Added (1,1)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 4: Dequeue (1,0), check neighbors
        All neighbors are water or visited
        Queue: [(1,1)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 5: Dequeue (1,1), check neighbors
        All neighbors are water or visited
        Queue: [] (empty)

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        BFS complete for first island.
        Island count: 1



Step 6: Continue scanning, find land at (2,2)
        Start BFS, add (2,2) to queue, mark visited
        Island count: 1 → 2
        Queue: [(2,2)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │  ← New island found!
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘



Step 7: Dequeue (2,2), no land neighbors
        Queue: [] (empty)

        BFS complete.
        Island count: 2



Step 8: Continue scanning, find land at (3,3)
        Start BFS, add (3,3) to queue, mark visited
        Island count: 2 → 3
        Queue: [(3,3)]

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ ✓ │  ← New island found!
  └───┴───┴───┴───┘



Step 9: Dequeue (3,3), no land neighbors
        Queue: [] (empty)

        BFS complete.
        Island count: 3

Final Result: 3 islands


class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
        ROWS, COLS = len(grid), len(grid[0])
        islands = 0

        def bfs(r, c):
            q = deque()
            grid[r][c] = "0"
            q.append((r, c))

            while q:
                row, col = q.popleft()
                for dr, dc in directions:
                    nr, nc = dr + row, dc + col
                    if (nr < 0 or nc < 0 or nr >= ROWS or
                        nc >= COLS or grid[nr][nc] == "0"
                    ):
                        continue
                    q.append((nr, nc))
                    grid[nr][nc] = "0"

        for r in range(ROWS):
            for c in range(COLS):
                if grid[r][c] == "1":
                    bfs(r, c)
                    islands += 1

        return islands

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns in the gridgrid.


3. Disjoint Set Union

Intuition

Think of every land cell ('1') as its own separate island initially.
When two land cells are adjacent (up, down, left, right), they actually belong to the same island, so we should merge them.

Disjoint Set Union (Union-Find) helps us:

  • Quickly connect adjacent land cells
  • Avoid counting the same island multiple times

Each successful merge reduces the total island count by 1.

Algorithm

  1. Treat each cell as a node and map (row, col) to a unique index.
  2. Initialize DSU for all cells.
  3. Traverse the grid:
    • If a cell is land ('1'), increment island count.
    • Check its 4 neighbors.
    • If a neighbor is also land:
      • Union the two cells.
      • If a union actually happens, decrement island count.
  4. After processing all cells, the remaining count is the number of islands.
  5. Return the island count.
Example - Dry Run

Input Grid:

grid = [
  ["1","1","0","0"],
  ["1","1","0","0"],
  ["0","0","1","0"],
  ["0","0","0","1"]
]

Visual Representation:

Input Grid (1 = land, 0 = water):

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

Cell Indices (row * 4 + col):

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ 0 │ 1 │ 2 │ 3 │
  ├───┼───┼───┼───┤
1 │ 4 │ 5 │ 6 │ 7 │
  ├───┼───┼───┼───┤
2 │ 8 │ 9 │10 │11 │
  ├───┼───┼───┼───┤
3 │12 │13 │14 │15 │
  └───┴───┴───┴───┘

Legend: █ = land ('1'), ░ = water ('0'), ✓ = visited

Step-by-Step Walkthrough:


Initial State:
  Parent: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
  Each cell is its own parent (separate component)



Step 1: Process cell (0,0) - index 0, it's land
        Island count: 0 → 1

        Check neighbor (1,0) - index 4, it's land
        Union(0, 4): different parents, merge!
        Island count: 1 → 0

        Check neighbor (0,1) - index 1, it's land
        Union(0, 1): different parents, merge!
        Island count: 0 → -1

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ █ │ ░ │ ░ │  ← Processing (0,0)
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        DSU: {0, 1, 4} are connected



Step 2: Process cell (0,1) - index 1, it's land
        Island count: -1 → 0

        Check neighbor (0,0) - index 0, it's land
        Union(1, 0): SAME parent (already connected)!
        No change

        Check neighbor (1,1) - index 5, it's land
        Union(1, 5): different parents, merge!
        Island count: 0 → -1

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │  ← Processing (0,1)
  ├───┼───┼───┼───┤
1 │ █ │ █ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        DSU: {0, 1, 4, 5} are connected



Step 3: Process cell (1,0) - index 4, it's land
        Island count: -1 → 0

        Check neighbor (0,0) - index 0
        Union(4, 0): SAME parent, no change

        Check neighbor (1,1) - index 5
        Union(4, 5): SAME parent, no change

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ █ │ ░ │ ░ │  ← Processing (1,0)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        DSU: {0, 1, 4, 5} still connected



Step 4: Process cell (1,1) - index 5, it's land
        Island count: 0 → 1

        Check neighbor (0,1) - index 1
        Union(5, 1): SAME parent, no change

        Check neighbor (1,0) - index 4
        Union(5, 4): SAME parent, no change

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │  ← Processing (1,1)
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ █ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        First island complete!
        DSU: {0, 1, 4, 5} unified
        Island count: 1



Step 5: Process cell (2,2) - index 10, it's land
        Island count: 1 → 2

        No land neighbors to union with

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │  ← New island found!
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ █ │
  └───┴───┴───┴───┘

        Island count: 2



Step 6: Process cell (3,3) - index 15, it's land
        Island count: 2 → 3

        No land neighbors to union with

    0   1   2   3
  ┌───┬───┬───┬───┐
0 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
1 │ ✓ │ ✓ │ ░ │ ░ │
  ├───┼───┼───┼───┤
2 │ ░ │ ░ │ ✓ │ ░ │
  ├───┼───┼───┼───┤
3 │ ░ │ ░ │ ░ │ ✓ │  ← New island found!
  └───┴───┴───┴───┘

        Island count: 3

Final DSU State:

Components:
  - Component 1: {0, 1, 4, 5} (first island)
  - Component 2: {10} (second island)
  - Component 3: {15} (third island)

Final Result: 3 islands


class DSU:
    def __init__(self, n):
        self.Parent = list(range(n + 1))
        self.Size = [1] * (n + 1)

    def find(self, node):
        if self.Parent[node] != node:
            self.Parent[node] = self.find(self.Parent[node])
        return self.Parent[node]

    def union(self, u, v):
        pu = self.find(u)
        pv = self.find(v)
        if pu == pv:
            return False
        if self.Size[pu] >= self.Size[pv]:
            self.Size[pu] += self.Size[pv]
            self.Parent[pv] = pu
        else:
            self.Size[pv] += self.Size[pu]
            self.Parent[pu] = pv
        return True

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        ROWS, COLS = len(grid), len(grid[0])
        dsu = DSU(ROWS * COLS)

        def index(r, c):
            return r * COLS + c

        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        islands = 0

        for r in range(ROWS):
            for c in range(COLS):
                if grid[r][c] == '1':
                    islands += 1
                    for dr, dc in directions:
                        nr, nc = r + dr, c + dc
                        if (nr < 0 or nc < 0 or nr >= ROWS or
                            nc >= COLS or grid[nr][nc] == "0"
                        ):
                            continue

                        if dsu.union(index(r, c), index(nr, nc)):
                            islands -= 1

        return islands

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns in the gridgrid.


Common Pitfalls

Not Marking Cells as Visited

When exploring an island, you must mark cells as visited (either by changing them to '0' or using a visited set). Forgetting this causes infinite loops as the DFS/BFS keeps revisiting the same cells.

# Wrong: doesn't mark visited, infinite loop
if grid[r][c] == '1':
    dfs(r + 1, c)
# Correct: mark as visited
if grid[r][c] == '1':
    grid[r][c] = '0'  # or visited.add((r, c))
    dfs(r + 1, c)

Counting Every Land Cell Instead of Islands

Each island should be counted once when you first encounter it. A common mistake is incrementing the count for every '1' cell rather than only incrementing when starting a new DFS/BFS traversal.

Incorrect Boundary Checks

Always verify that row and column indices are within bounds before accessing the grid. Off-by-one errors or missing boundary checks cause index out of bounds errors.

# Wrong: checks after access
if grid[nr][nc] == '1' and 0 <= nr < rows:
# Correct: check bounds first
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1':

Diagonal Connections

This problem only considers horizontal and vertical connections (4-directional). Including diagonal neighbors incorrectly merges separate islands and undercounts the total.