Facebook Pixel

200. Number of Islands

Problem Description

You are given a 2D grid of size m x n where each cell contains either '1' (representing land) or '0' (representing water). Your task is to count the total number of islands in this grid.

An island is defined as a group of adjacent land cells ('1's) that are connected horizontally or vertically. Diagonal connections don't count - only cells that share an edge are considered connected. Each island is completely surrounded by water ('0's).

The entire grid is surrounded by water on all four edges (you can assume there's water beyond the grid boundaries).

For example, if you have a grid like:

1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1

This grid contains 3 islands:

  • Island 1: The connected '1's in the top-left corner (2x2 block)
  • Island 2: The single '1' in the middle
  • Island 3: The two connected '1's in the bottom-right

The solution uses Depth-First Search (DFS) to traverse each island. When a '1' is found, it triggers a DFS that marks all connected land cells as visited (by changing them to '0'), effectively "sinking" the entire island. Each time a new unvisited '1' is found in the main loop, it represents a new island, so the counter is incremented. The dirs array (-1, 0, 1, 0, -1) is used with pairwise to generate the four directional movements: up (-1, 0), right (0, 1), down (1, 0), and left (0, -1).

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The 2D grid can be viewed as a graph where each cell is a node, and adjacent cells (horizontally and vertically) are connected by edges. We need to find connected components of '1's.

Is it a tree?

  • No: The problem is not about a tree structure. We have a general graph with potential cycles (multiple land cells can form various shapes).

Is the problem related to directed acyclic graphs?

  • No: The connections between adjacent cells are undirected (if cell A is adjacent to cell B, then B is also adjacent to A).

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between nodes. We're counting the number of distinct connected components (islands).

Does the problem involve connectivity?

  • Yes: The core of the problem is identifying connected components - groups of '1's that are connected horizontally or vertically.

Does the problem have small constraints?

  • Yes: While the grid can be up to 300x300, we can afford to visit each cell, and the DFS approach is efficient enough for these constraints.

DFS/backtracking?

  • Yes: DFS is perfect for exploring all cells in a connected component (island) and marking them as visited.

Conclusion: The flowchart suggests using DFS to solve this connectivity problem. DFS allows us to explore each island completely, marking all connected land cells as visited before moving on to find the next unvisited island.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think of this problem as finding connected components in a graph. Each land cell ('1') is like a node, and two land cells are connected if they're adjacent horizontally or vertically.

When we encounter an unvisited land cell, we've discovered a new island. But how big is this island? We need to explore all land cells connected to it. This is where DFS comes in naturally - starting from one land cell, we want to visit all reachable land cells that form the same island.

The clever part is how we handle "visited" cells. Instead of maintaining a separate visited array, we can modify the grid itself - changing '1' to '0' as we visit each cell. This "sinks" the island as we explore it, preventing us from counting the same island multiple times.

The algorithm flow becomes simple:

  1. Scan through the entire grid
  2. When we find a '1', we've found a new island - increment our island counter
  3. Use DFS to explore and "sink" the entire island by turning all connected '1's into '0's
  4. Continue scanning - any '1' we find now must belong to a different island

The beauty of this approach is that each cell is visited at most twice - once during the main grid scan, and potentially once more during a DFS traversal. The DFS naturally handles islands of any shape or size, following the connections until all parts of an island are explored.

The dirs array pattern (-1, 0, 1, 0, -1) paired with pairwise is an elegant way to generate the four directions: consecutive pairs give us (-1, 0) for up, (0, 1) for right, (1, 0) for down, and (0, -1) for left.

Solution Approach

The implementation uses DFS to traverse and mark connected land cells. Let's walk through the solution step by step:

Main Algorithm Structure:

  1. Initialize counters and dimensions:

    • ans = 0 to count islands
    • dirs = (-1, 0, 1, 0, -1) for direction vectors
    • m, n = len(grid), len(grid[0]) to get grid dimensions
  2. DFS Helper Function:

    def dfs(i, j):
        grid[i][j] = '0'  # Mark current cell as visited (sink it)
        for a, b in pairwise(dirs):
            x, y = i + a, j + b
            if 0 <= x < m and 0 <= y < n and grid[x][y] == '1':
                dfs(x, y)

    The DFS function:

    • Takes coordinates (i, j) of a land cell
    • Immediately marks it as water '0' to prevent revisiting
    • Uses pairwise(dirs) to generate four direction pairs: (-1, 0), (0, 1), (1, 0), (0, -1)
    • For each direction, calculates new coordinates (x, y)
    • Checks if the new position is:
      • Within bounds: 0 <= x < m and 0 <= y < n
      • Is land: grid[x][y] == '1'
    • Recursively calls DFS on valid land neighbors
  3. Main Loop:

    for i in range(m):
        for j in range(n):
            if grid[i][j] == '1':
                dfs(i, j)
                ans += 1
    • Iterates through every cell in the grid
    • When a '1' is found (unvisited land):
      • Calls dfs(i, j) to explore and sink the entire island
      • Increments the island counter
    • Since DFS converts all connected '1's to '0's, each island is counted exactly once

Data Structures Used:

  • 2D Grid: Modified in-place to track visited cells
  • Recursion Stack: Implicit stack used by DFS for traversal
  • Direction Vector: Compact representation of four directions using pairwise

Time Complexity: O(m Γ— n) where m and n are grid dimensions. Each cell is visited at most once.

Space Complexity: O(m Γ— n) in worst case for the recursion stack (when the entire grid is one island).

The elegance of this solution lies in its simplicity - by modifying the grid in-place and using DFS to explore each island completely, we avoid the need for additional visited tracking structures while ensuring each island is counted exactly once.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example to illustrate how the DFS solution works:

Given Grid:

1 1 0
0 1 0
1 0 1

Step-by-step Execution:

Initial State:

  • ans = 0 (island counter)
  • Start scanning from position (0,0)

Step 1: Found '1' at (0,0)

  • This is unvisited land, so we found a new island!
  • Call dfs(0, 0) to explore this island
  • DFS exploration:
    • Mark (0,0) as '0' β†’ Grid becomes:

      0 1 0
      0 1 0
      1 0 1
    • Check 4 directions from (0,0):

      • Up: out of bounds
      • Right: (0,1) is '1' β†’ Call dfs(0, 1)
      • Down: (1,0) is '0' β†’ Skip
      • Left: out of bounds
    • From (0,1), mark it as '0' β†’ Grid becomes:

      0 0 0
      0 1 0
      1 0 1
    • Check 4 directions from (0,1):

      • Up: out of bounds
      • Right: (0,2) is '0' β†’ Skip
      • Down: (1,1) is '1' β†’ Call dfs(1, 1)
      • Left: (0,0) is '0' β†’ Skip
    • From (1,1), mark it as '0' β†’ Grid becomes:

      0 0 0
      0 0 0
      1 0 1
    • Check 4 directions from (1,1):

      • All neighbors are either '0' or out of bounds
    • DFS completes, returning to main loop

  • Increment ans = 1

Step 2: Continue scanning

  • (0,1) is '0' β†’ Skip
  • (0,2) is '0' β†’ Skip
  • (1,0) is '0' β†’ Skip
  • (1,1) is '0' β†’ Skip
  • (1,2) is '0' β†’ Skip

Step 3: Found '1' at (2,0)

  • This is unvisited land, new island found!
  • Call dfs(2, 0)
  • Mark (2,0) as '0' β†’ Grid becomes:
    0 0 0
    0 0 0
    0 0 1
  • Check 4 directions: all are '0' or out of bounds
  • Increment ans = 2

Step 4: Continue scanning

  • (2,1) is '0' β†’ Skip

Step 5: Found '1' at (2,2)

  • This is unvisited land, new island found!
  • Call dfs(2, 2)
  • Mark (2,2) as '0' β†’ Grid becomes:
    0 0 0
    0 0 0
    0 0 0
  • Check 4 directions: all are '0' or out of bounds
  • Increment ans = 3

Final Result: ans = 3 islands

The key insight: Each time we find a '1' in the main loop, we've discovered a new island. The DFS ensures we explore and "sink" the entire island before continuing, preventing double-counting.

Solution Implementation

1class Solution:
2    def numIslands(self, grid: List[List[str]]) -> int:
3        """
4        Count the number of islands in a 2D grid.
5        An island is formed by connecting adjacent lands ('1') horizontally or vertically.
6      
7        Args:
8            grid: 2D grid of '1's (land) and '0's (water)
9      
10        Returns:
11            Number of islands in the grid
12        """
13      
14        def dfs(row: int, col: int) -> None:
15            """
16            Depth-first search to mark all connected land cells as visited.
17            Modifies the grid in-place by changing '1' to '0'.
18          
19            Args:
20                row: Current row index
21                col: Current column index
22            """
23            # Mark current cell as visited (water)
24            grid[row][col] = '0'
25          
26            # Check all 4 adjacent directions (up, right, down, left)
27            for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
28                next_row, next_col = row + dr, col + dc
29              
30                # If the adjacent cell is within bounds and is unvisited land
31                if (0 <= next_row < rows and 
32                    0 <= next_col < cols and 
33                    grid[next_row][next_col] == '1'):
34                    # Recursively explore the adjacent land
35                    dfs(next_row, next_col)
36      
37        # Initialize island counter
38        island_count = 0
39      
40        # Get grid dimensions
41        rows, cols = len(grid), len(grid[0])
42      
43        # Traverse each cell in the grid
44        for i in range(rows):
45            for j in range(cols):
46                # If we find unvisited land, it's a new island
47                if grid[i][j] == '1':
48                    # Explore and mark the entire island
49                    dfs(i, j)
50                    # Increment island count
51                    island_count += 1
52      
53        return island_count
54
1class Solution {
2    // Instance variables to store grid and its dimensions
3    private char[][] grid;
4    private int rows;
5    private int cols;
6
7    /**
8     * Counts the number of islands in a 2D grid.
9     * An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.
10     * 
11     * @param grid 2D grid map of '1's (land) and '0's (water)
12     * @return the number of islands
13     */
14    public int numIslands(char[][] grid) {
15        // Initialize grid dimensions
16        this.rows = grid.length;
17        this.cols = grid[0].length;
18        this.grid = grid;
19      
20        int islandCount = 0;
21      
22        // Traverse each cell in the grid
23        for (int row = 0; row < rows; row++) {
24            for (int col = 0; col < cols; col++) {
25                // If current cell is land ('1'), start DFS to mark entire island
26                if (grid[row][col] == '1') {
27                    dfs(row, col);
28                    islandCount++;
29                }
30            }
31        }
32      
33        return islandCount;
34    }
35
36    /**
37     * Performs depth-first search to mark all connected land cells as visited.
38     * Marks visited land cells by changing them from '1' to '0'.
39     * 
40     * @param row current row index
41     * @param col current column index
42     */
43    private void dfs(int row, int col) {
44        // Mark current land cell as visited by setting it to water ('0')
45        grid[row][col] = '0';
46      
47        // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
48        // Using pairs: (-1,0), (0,1), (1,0), (0,-1)
49        int[] directions = {-1, 0, 1, 0, -1};
50      
51        // Explore all 4 adjacent cells
52        for (int i = 0; i < 4; i++) {
53            int newRow = row + directions[i];
54            int newCol = col + directions[i + 1];
55          
56            // Check if adjacent cell is within bounds and is unvisited land
57            if (newRow >= 0 && newRow < rows && 
58                newCol >= 0 && newCol < cols && 
59                grid[newRow][newCol] == '1') {
60                // Recursively explore the adjacent land cell
61                dfs(newRow, newCol);
62            }
63        }
64    }
65}
66
1class Solution {
2public:
3    int numIslands(vector<vector<char>>& grid) {
4        // Get grid dimensions
5        int rows = grid.size();
6        int cols = grid[0].size();
7        int islandCount = 0;
8      
9        // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
10        // Using pairs: (-1,0), (0,1), (1,0), (0,-1)
11        int directions[5] = {-1, 0, 1, 0, -1};
12      
13        // DFS function to mark all connected land cells as visited
14        function<void(int, int)> dfs = [&](int row, int col) {
15            // Mark current cell as visited by changing '1' to '0'
16            grid[row][col] = '0';
17          
18            // Explore all 4 adjacent directions
19            for (int k = 0; k < 4; ++k) {
20                int nextRow = row + directions[k];
21                int nextCol = col + directions[k + 1];
22              
23                // Check if the adjacent cell is within bounds and is unvisited land
24                if (nextRow >= 0 && nextRow < rows && 
25                    nextCol >= 0 && nextCol < cols && 
26                    grid[nextRow][nextCol] == '1') {
27                    // Recursively visit the adjacent land cell
28                    dfs(nextRow, nextCol);
29                }
30            }
31        };
32      
33        // Traverse the entire grid
34        for (int i = 0; i < rows; ++i) {
35            for (int j = 0; j < cols; ++j) {
36                // If we find an unvisited land cell, it's a new island
37                if (grid[i][j] == '1') {
38                    // Use DFS to mark all connected land cells
39                    dfs(i, j);
40                    // Increment island count
41                    ++islandCount;
42                }
43            }
44        }
45      
46        return islandCount;
47    }
48};
49
1/**
2 * Counts the number of islands in a 2D grid
3 * An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically
4 * @param grid - 2D array where '1' represents land and '0' represents water
5 * @returns The number of islands
6 */
7function numIslands(grid: string[][]): number {
8    const rowCount: number = grid.length;
9    const columnCount: number = grid[0].length;
10    let islandCount: number = 0;
11  
12    /**
13     * Depth-first search to mark all connected land cells as visited
14     * @param row - Current row index
15     * @param column - Current column index
16     */
17    const markIslandVisited = (row: number, column: number): void => {
18        // Check if current cell is out of bounds or not a land cell
19        if (grid[row]?.[column] !== '1') {
20            return;
21        }
22      
23        // Mark current land cell as visited by changing it to water
24        grid[row][column] = '0';
25      
26        // Recursively visit all four adjacent cells (up, down, left, right)
27        markIslandVisited(row + 1, column); // Check cell below
28        markIslandVisited(row - 1, column); // Check cell above
29        markIslandVisited(row, column + 1); // Check cell to the right
30        markIslandVisited(row, column - 1); // Check cell to the left
31    };
32  
33    // Iterate through each cell in the grid
34    for (let row: number = 0; row < rowCount; row++) {
35        for (let column: number = 0; column < columnCount; column++) {
36            // If we find an unvisited land cell, it's a new island
37            if (grid[row][column] === '1') {
38                // Mark all connected land cells as visited
39                markIslandVisited(row, column);
40                // Increment the island counter
41                islandCount++;
42            }
43        }
44    }
45  
46    return islandCount;
47}
48

Time and Space Complexity

Time Complexity: O(m * n) where m is the number of rows and n is the number of columns in the grid.

  • The outer loops iterate through every cell in the grid, which takes O(m * n) time.
  • For each cell that contains '1', we perform a DFS traversal. In the worst case, if the entire grid is filled with '1's forming one large island, the DFS will visit every cell once.
  • Each cell is visited at most twice: once by the outer loop iteration and potentially once during a DFS traversal from a neighboring cell.
  • The pairwise function with dirs = (-1, 0, 1, 0, -1) generates 4 direction pairs: [(-1, 0), (0, 1), (1, 0), (0, -1)], representing up, right, down, and left movements. This operation is O(1) for each cell.
  • Overall, every cell is processed a constant number of times, resulting in O(m * n) time complexity.

Space Complexity: O(m * n) in the worst case.

  • The space complexity is dominated by the recursive call stack during DFS traversal.
  • In the worst case, if the entire grid forms a single island with all cells containing '1', the DFS recursion depth could reach O(m * n) for a snake-like pattern that visits every cell sequentially.
  • The dirs variable takes O(1) space.
  • The algorithm modifies the input grid in-place by marking visited cells as '0', so no additional space is used for tracking visited cells.
  • Therefore, the space complexity is O(m * n) due to the recursion stack.

Common Pitfalls

1. Modifying the Original Grid

Problem: The solution modifies the input grid in-place by changing '1's to '0's. This destroys the original data, which might be needed later or violates constraints if the grid should remain unchanged.

Example Scenario:

grid = [['1','1','0'], ['0','1','0']]
solution = Solution()
count = solution.numIslands(grid)
# grid is now [['0','0','0'], ['0','0','0']] - original data lost!

Solution: Create a separate visited tracking structure or make a deep copy:

def numIslands(self, grid: List[List[str]]) -> int:
    # Option 1: Use a visited set
    visited = set()
  
    def dfs(row, col):
        visited.add((row, col))
        for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
            next_row, next_col = row + dr, col + dc
            if (0 <= next_row < rows and 
                0 <= next_col < cols and 
                grid[next_row][next_col] == '1' and
                (next_row, next_col) not in visited):
                dfs(next_row, next_col)
  
    # Option 2: Work with a copy
    grid_copy = [row[:] for row in grid]
    # Then use grid_copy in the original algorithm

2. Stack Overflow with Large Islands

Problem: For very large grids where one island covers most cells, the recursive DFS can cause stack overflow due to deep recursion.

Example: A 1000Γ—1000 grid filled entirely with '1's would require recursion depth of up to 1,000,000.

Solution: Use iterative DFS with an explicit stack:

def dfs_iterative(row, col):
    stack = [(row, col)]
    while stack:
        curr_row, curr_col = stack.pop()
        if (curr_row < 0 or curr_row >= rows or 
            curr_col < 0 or curr_col >= cols or
            grid[curr_row][curr_col] != '1'):
            continue
      
        grid[curr_row][curr_col] = '0'
      
        # Add all 4 neighbors to stack
        stack.extend([(curr_row-1, curr_col), (curr_row+1, curr_col),
                     (curr_row, curr_col-1), (curr_row, curr_col+1)])

3. Type Confusion with Grid Values

Problem: Mixing string '1'/'0' with integer 1/0 comparisons, leading to subtle bugs.

Example:

# Wrong - comparing string with integer
if grid[row][col] == 1:  # This will always be False!
    dfs(row, col)

# Correct
if grid[row][col] == '1':
    dfs(row, col)

Solution: Be consistent with data types and consider converting at the start:

# Convert to integers if needed for consistency
grid = [[int(cell) for cell in row] for row in grid]
# Then use integer comparisons throughout

4. Incorrect Boundary Checking Order

Problem: Accessing grid elements before checking boundaries causes IndexError.

Example:

# Wrong - accesses grid before boundary check
if grid[next_row][next_col] == '1' and 0 <= next_row < rows:
    dfs(next_row, next_col)  # IndexError if next_row is out of bounds!

# Correct - check boundaries first
if 0 <= next_row < rows and 0 <= next_col < cols and grid[next_row][next_col] == '1':
    dfs(next_row, next_col)

Solution: Always validate indices before accessing array elements, using short-circuit evaluation.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More