Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 55

Grid Traversal: BFS and DFS for Island Problems

Recommended resources

Hello! Welcome back to our journey through graph algorithms.

In our last lesson, we implemented Depth-First Search (DFS) for graph exploration and path finding, adding it to our toolkit alongside Breadth-First Search (BFS). You learned how DFS's "go deep" strategy, powered by a stack (often the recursive call stack), is perfect for pathing problems.

Today, we'll apply these powerful traversal algorithms to a classic and high-ROI interview pattern: grid-based graph problems. Our goal is to solve problems like "Number of Islands" using either BFS or DFS. This will teach you how to view a 2D grid as an implicit graph and apply the traversal techniques you already know to solve a new class of problems.

1. From Adjacency Lists to Grids: The Implicit Graph

So far, we've worked with graphs defined explicitly by adjacency lists. However, many interview problems present graphs implicitly. A 2D grid is the most common example.

Think of it this way:

  • Nodes: Each cell (r, c) in the grid is a node.
  • Edges: An edge exists between two cells if they are "adjacent" and satisfy a certain condition (e.g., both are land). In most problems, adjacency is defined as sharing a side (horizontally or vertically), but sometimes it includes diagonals.

The key is that you don't need to build an adjacency list first. You can compute the neighbors of any given cell (r, c) on the fly: (r-1, c), (r+1, c), (r, c-1), and (r, c+1). This is the core idea behind applying graph traversals to grids.


2. The 'Number of Islands' Problem

The canonical problem for this pattern is "Number of Islands" (LeetCode 200), a favorite at companies like Google and Amazon. You're given a 2D grid where '1's represent land and '0's represent water. Your task is to count the number of distinct islands. An island is a group of '1's connected horizontally or vertically.

To get a feel for the problem and the intuitive approach, watch the beginning of this excellent video from NeetCode.

NUMBER OF ISLANDS - Leetcode 200 - Python

This video introduces the 'Number of Islands' problem and visualizes how you might solve it manually before translating that intuition into an algorithm.

Watch the first 3 minutes of the video (from 00:00 to 03:06). Focus on understanding the problem definition and the high-level strategy of finding a piece of land and then exploring all its connected parts.


3. The General Strategy: Counting Connected Components

As you saw in the video, the problem boils down to finding and counting "connected components" in the grid-graph. This gives us a universal template that works with either DFS or BFS.

The pattern is:

  1. Initialize an islands_count to 0.
  2. Create a way to track visited cells to avoid recounting. A 2D boolean array or a set of (row, col) tuples works well.
  3. Iterate through every cell (r, c) in the grid.
  4. If the current cell (r, c) is land ('1') and has not been visited yet:
    • You've discovered a new island! Increment islands_count.
    • Launch a graph traversal (DFS or BFS) starting from (r, c).
    • This traversal's job is to find every piece of land connected to (r, c) and mark it as visited. This effectively "sinks" the entire island, ensuring you don't count any part of it again.
  5. After iterating through all cells, return islands_count.

This "iterate and flood-fill" approach is a fundamental pattern for many grid problems.

Depth-First Search Introduction | Hello ...

The article from Hello Interview frames this exact pattern as 'Counting Connected Components'. Reading this will solidify the general strategy before we dive into the specific implementations.

Read the sections 'Counting Connected Components' and 'When to Use DFS'. This will reinforce the pattern and connect it back to our previous lesson on DFS.


4. Implementation with Depth-First Search (DFS)

DFS is a natural fit for this problem, as its recursive nature makes for clean and concise code. When we find an unvisited '1', we'll start a recursive dfs function that explores that island to its "dead ends" before backtracking.

This video provides a fantastic step-by-step implementation using a recursive DFS.

Number of Islands - LeetCode 200 Python

This video by Deepti Talesra walks through the DFS approach, from the main loop to the recursive helper function. It provides a very clear explanation of the call stack's role in the traversal.

Watch from 01:24 to 10:02. The presenter explains the strategy, codes the main loop, implements the recursive DFS helper, and then traces an example to show how the call stack manages the exploration.

Let's break down the dfs helper function's logic:

def dfs(grid, r, c):
    # 1. Base cases for stopping the recursion:
    # - Out of bounds
    # - Current cell is water
    if r < 0 or c < 0 or r >= len(grid) or c >= len(grid[0]) or grid[r][c] == '0':
        return

    # 2. Mark as visited to prevent infinite loops and re-counting.
    # Here, we modify the grid in-place.
    grid[r][c] = '0' 

    # 3. Recursive step: visit all 4 neighbors
    dfs(grid, r + 1, c) # Down
    dfs(grid, r - 1, c) # Up
    dfs(grid, r, c + 1) # Right
    dfs(grid, r, c - 1) # Left

A note on marking visited cells:

  • In-place modification: Changing '1' to '0' (or '#') is space-efficient ( extra space, besides the recursion stack) and common in competitive programming and interviews. The downside is that it mutates the input grid.
  • Auxiliary visited set/matrix: Using a separate visited set or boolean matrix (e.g., visited.add((r, c))) is cleaner, as it doesn't modify the input. This uses extra space.

For an interview, you can mention both and ask if modifying the input is acceptable.


5. Implementation with Breadth-First Search (BFS)

We can just as easily use BFS for the traversal part. Instead of a deep recursive dive, BFS will explore the island layer by layer using a queue. The main for-loop structure for iterating through the grid remains identical.

The NeetCode video you started earlier continues by implementing the BFS solution.

NUMBER OF ISLANDS - Leetcode 200 - Python

Let's return to the NeetCode video to see a full BFS implementation. This will demonstrate how to swap the recursive DFS for an iterative BFS with a queue.

Watch from 03:06 to 10:57. This segment details how BFS explores an island layer by layer and then provides a full Python implementation, including managing the queue and visited set.

The BFS helper logic looks like this:

from collections import deque

def bfs(grid, r, c, visited):
    q = deque([(r, c)])
    visited.add((r, c))
    
    while q:
        row, col = q.popleft()
        
        directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]
        for dr, dc in directions:
            nr, nc = row + dr, col + dc
            
            # Check if neighbor is valid and unvisited
            if (nr in range(len(grid)) and
                nc in range(len(grid[0])) and
                grid[nr][nc] == '1' and
                (nr, nc) not in visited):
                
                q.append((nr, nc))
                visited.add((nr, nc))
BFS Iteration 10: Number of Islands Grid State
CAPTION: This image illustrates the state of a grid after a BFS traversal has completed for one island. The queue is empty, and the `islandCount` has been incremented. The cells belonging to the discovered island are marked as visited.
Test your understanding!

In the NeetCode video (at 10:57), the presenter mentions you can change the BFS into a DFS by changing one line of code. Which line is it, and what do you change it to? Why does this work?

Show answer

You would change row, col = q.popleft() to row, col = q.pop().

A queue's popleft() operation enforces First-In, First-Out (FIFO) behavior, which is the foundation of BFS. A list or deque's pop() operation (without arguments) removes from the end, which is Last-In, First-Out (LIFO) behavior. This effectively turns the queue into a stack, and an iterative traversal using a stack is precisely what iterative DFS is!


6. Complexity and Final Comparison

For an grid:

  • Time Complexity:

    • Both DFS and BFS solutions require traversing the entire grid once with the outer loops. The inner traversals (DFS/BFS) will visit each '1' cell exactly once across the entire execution. Therefore, the total time is proportional to the number of cells.
  • Space Complexity: in the worst case

    • BFS: The maximum size of the queue determines the space. In a worst-case scenario (like a checkerboard), the queue could hold roughly half the cells.
    • DFS (recursive): The space is determined by the maximum depth of the recursion stack. The worst case is a single, long, snake-like island that winds through the entire grid, leading to a recursion depth of .
    • Note: If you use an auxiliary visited matrix/set, that also contributes to the space complexity.

For this problem, both DFS and BFS are perfectly valid. The choice is often a matter of style.

DFS vs. BFS for Number of Islands Problem
CAPTION: A visual comparison of how DFS and BFS explore an island. DFS (left) follows a single path deep into the island before backtracking. BFS (right) explores in concentric layers from the starting point.

A Note on Variations: The classic "Number of Islands" problem considers 4-directional adjacency. Some variations may define islands by including diagonal connections (8-directional). The only change to your code would be to expand the directions array to include (1, 1), (1, -1), (-1, 1), and (-1, -1). The "Number of Islands" article from GeeksForGeeks (LINK) provides a clear example of an 8-directional implementation if you're curious.


Conclusion

Congratulations! You've successfully applied your knowledge of graph traversals to a new and important problem domain. Grid problems are extremely common in interviews, and the "Number of Islands" pattern is a gateway to solving many of them.

Key Takeaways:

  • Grids are Implicit Graphs: You can perform graph traversals on them by calculating neighbors on the fly.
  • Count Connected Components: The core strategy for "Number of Islands" is to iterate through each cell and launch a traversal (DFS or BFS) for any unvisited component, incrementing a counter each time.
  • Sink the Island: The traversal's main job is to find all parts of the current island and mark them as visited so they aren't counted again.
  • DFS vs. BFS: Both work and have the same time complexity. DFS can be more concise recursively, while BFS is always iterative and can be better for finding shortest paths in grids (a topic for another day!).

Preview of the Next Lesson:

So far, we've only dealt with unweighted graphs, where every edge has the same "cost". In the next lesson, we'll level up by tackling weighted graphs. You will implement Dijkstra's algorithm to find the shortest path in a weighted graph, a cornerstone algorithm with countless real-world applications.