Data Structures & Algorithms (Graphs): Number of Islands

November 11, 2025

Given an m x n 2-D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands.

NOTE: 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.

example

Solve Here: Leetcode 200


Intuition

Iterate over the input grid marking each cell as visited. Whenever we encounter a land cell ('1'), we treat it as a new island, increment the island count, and then use DFS or BFS to traverse all its adjacent land cells (up, down, left, right), marking them as visited. This ensures that all connected land cells are counted as part of the same island and not revisited.

public int numIslands(char[][] grid) {

  if (grid.length == 0) return 0;
  
  int islandCount = 0;
  boolean[][] visited = new boolean[grid.length][grid[0].length];
  
  for(int i = 0; i < grid.length; i++) {
    for(int j = 0; j < grid[0].length; j++) {
      if (grid[i][j] == '1' && visited[i][j] == false) {
        islandCount++;
        // dfs(grid, i, j, visited) or bfs(grid, i, j, visited)
      }
    }
  }

  return islandCount;

}


Approach 1: Depth-First Search (DFS)

Use recursion to traverse the adjacent land cells (up, down, left, right) and mark them as visited.

Hypothesis

Let F(grid, i, j, visited) be a function that marks the current cell as visited and go explore its neighbourhood (up, down, left, right) for unvisited land cells.

Recursive Steps

// 1. Mark the current node as visited
visited[i][j] = true;

// 2. Visit the neighbour at the top
dfs(grid, i-1, j, visited);
// 3. Visit the neighbour on the left
dfs(grid, i, j-1, visited);
// 4. Visit the neighbour at the bottom
dfs(grid, i+1, j, visited);
// 5. Visit the neighbour on the right
dfs(grid, i, j+1, visited);

Base Conditions

If the current cell is out of bounds or is not a land cell or is already visited, return.

if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0' || visited[i][j] == true) {
    return;
}

Visualization

0
1
2
3
4
0
1
1
0
0
0
1
1
1
0
0
0
2
0
0
1
0
0
3
0
0
0
1
1
Islands found0
No islands found yet.

Scan cells left → right, top → bottom. On each unvisited land cell, run DFS to flood the whole island.

Legend
Active (DFS)WaterUnvisited landScan cursor
0/31

Time Complexity

Since, we are visiting each cell in the grid at most twice, the overall time complexity = O(m * n).

Space Complexity

We are using a visited array of size m * n to store the visited cells. The recursive call stack can go up to a maximum depth of O(m * n) in the worst case (when all cells are land cells).

Hence, overall space complexity = O(m * n).


Approach 2: Breadth-First Search (BFS)

For each land cell ('1'), add all its adjacent land cells (up, down, left, right) to a queue and mark them as visited, potentially creating the next layer of land cells for exploration.

Algorithm

class Cell {
    int x;
    int y;

    Cell(int i, int j) {
        this.x = i;
        this.y = j;
    }
}

private boolean isInBound(int i, int j, int m, int n) {
    return i >= 0 && i < m && j >=0 && j < n; 
}

private void bfs(char[][] grid, int i, int j, boolean[][] visited) {

    Queue<Cell> queue = new LinkedList<>();
    queue.offer(new Cell(i, j));
    visited[i][j] = true;

    while (!queue.isEmpty()) {
        Cell cell = queue.poll();
        int x = cell.x;
        int y = cell.y;

        // Explore neighbour at the top
        if (isInBound(x-1, y, grid.length, grid[0].length) && grid[x-1][y] == '1' && visited[x-1][y] == false) {
            queue.offer(new Cell(x-1, y));
            visited[x-1][y] = true;
        }
        // Explore neighbour on the left
        if (isInBound(x, y-1, grid.length, grid[0].length) && grid[x][y-1] == '1' && visited[x][y-1] == false) {
            queue.offer(new Cell(x, y-1));
            visited[x][y-1] = true;
        }

        // Explore neighbour at the bottom
        if (isInBound(x+1, y, grid.length, grid[0].length) && grid[x+1][y] == '1' && visited[x+1][y] == false) {
            queue.offer(new Cell(x+1, y));
            visited[x+1][y] = true;
        }

        // Explore neighbout on the right
        if (isInBound(x, y+1, grid.length, grid[0].length) && grid[x][y+1] == '1' && visited[x][y+1] == false) {
            queue.offer(new Cell(x, y+1));
            visited[x][y+1] = true;
        }

    }

}

Visualization

0
1
2
3
4
0
1
1
0
0
0
1
1
1
0
0
0
2
0
0
1
0
0
3
0
0
0
1
1
QUEUEempty
Islands found0
No islands found yet.

Scan cells left → right, top → bottom. On each unvisited land cell, enqueue it and run BFS to flood the whole island.

Legend
Active (dequeued)In queueWaterUnvisited landScan cursor
0/31

Time Complexity

Since, we are visiting each cell in the grid at most twice, the overall time complexity = O(m * n).

Space Complexity

In a grid completely filled with land, the maximum number of cells in the queue at any given time is bounded by the smaller dimension of the grid, i.e., min(m, n).

Additionally, we are using a visited array of size m * n to store the visited cells. Therefore, the overall space complexity = O(min(m, n)) + O(m * n) ~ O(m * n).

bfs-space-complexity