🚧 We are still building the lighthouse. Hang tight!

projectlighthouse logoprojectlighthouse

Sign in with your Google or GitHub account to get started

Matrix BFS

Breadth-first search on matrices for shortest path problems. Learn when BFS guarantees optimal paths.

While DFS explores as deep as possible, BFS explores level by level. This property makes BFS ideal for finding the shortest path in unweighted grids.

When to Use BFS vs DFS

| Use Case | BFS | DFS | |----------|-----|-----| | Shortest path (unweighted) | Yes | No | | Explore all paths | No | Yes | | Level-by-level processing | Yes | No | | Memory (wide graphs) | High | Low | | Memory (deep graphs) | Low | High |

Basic BFS on Grid

use std::collections::{HashSet, VecDeque};

fn bfs(grid: &[Vec<i32>], start_row: usize, start_col: usize) {
    let rows = grid.len();
    let cols = grid[0].len();
    let mut visited = HashSet::new();
    let mut queue = VecDeque::new();

    queue.push_back((start_row, start_col));
    visited.insert((start_row, start_col));

    let directions = [(0i32, 1i32), (0, -1), (1, 0), (-1, 0)];

    while let Some((r, c)) = queue.pop_front() {
        // Process cell (r, c)

        for &(dr, dc) in &directions {
            let nr = r as i32 + dr;
            let nc = c as i32 + dc;

            if nr >= 0 && nr < rows as i32 && nc >= 0 && nc < cols as i32 {
                let (nr, nc) = (nr as usize, nc as usize);
                if !visited.contains(&(nr, nc)) && grid[nr][nc] == 1 {
                    visited.insert((nr, nc));
                    queue.push_back((nr, nc));
                }
            }
        }
    }
}

Problem: Shortest Path in Binary Matrix

Find the shortest path from top-left to bottom-right (8-directional):

Grid:              Path length: 4
0 1 0              →  .  .
0 0 0              ↓  ↘  .
1 1 0              .  .  →
def shortest_path_binary_matrix(grid):
    if not grid or grid[0][0] == 1 or grid[-1][-1] == 1:
        return -1

    n = len(grid)
    if n == 1:
        return 1

    # 8 directions including diagonals
    directions = [
        (-1, 0), (1, 0), (0, -1), (0, 1),
        (-1, -1), (-1, 1), (1, -1), (1, 1)
    ]

    queue = deque([(0, 0, 1)])  # (row, col, distance)
    visited = {(0, 0)}

    while queue:
        r, c, dist = queue.popleft()

        for dr, dc in directions:
            nr, nc = r + dr, c + dc

            if 0 <= nr < n and 0 <= nc < n:
                if (nr, nc) not in visited and grid[nr][nc] == 0:
                    if nr == n - 1 and nc == n - 1:
                        return dist + 1

                    visited.add((nr, nc))
                    queue.append((nr, nc, dist + 1))

    return -1

Why BFS Gives Shortest Path

BFS explores all cells at distance d before any cell at distance d+1. So the first time we reach the destination, we've found the shortest path.

Problem: Rotting Oranges

Find minimum time for all oranges to rot (multi-source BFS):

0 = empty, 1 = fresh, 2 = rotten

Before:     After 1 min:   After 2 min:
2 1 1       2 2 1          2 2 2
1 1 0   →   2 1 0      →   2 2 0
0 1 1       0 1 1          0 2 1

After 3 min:   After 4 min:
2 2 2          2 2 2
2 2 0          2 2 0
0 2 2          0 2 2

Answer: 4 minutes
def oranges_rotting(grid):
    if not grid:
        return -1

    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh_count = 0

    # Find all rotten oranges and count fresh
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 2:
                queue.append((r, c))
            elif grid[r][c] == 1:
                fresh_count += 1

    if fresh_count == 0:
        return 0

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

    while queue:
        # Process all oranges that rot at current minute
        for _ in range(len(queue)):
            r, c = queue.popleft()

            for dr, dc in directions:
                nr, nc = r + dr, c + dc

                if 0 <= nr < rows and 0 <= nc < cols:
                    if grid[nr][nc] == 1:
                        grid[nr][nc] = 2
                        fresh_count -= 1
                        queue.append((nr, nc))

        if queue:
            minutes += 1

    return minutes if fresh_count == 0 else -1

Problem: 01 Matrix (Distance to Nearest 0)

Find distance from each cell to the nearest 0:

def update_matrix(mat):
    if not mat:
        return mat

    rows, cols = len(mat), len(mat[0])
    queue = deque()
    result = [[float('inf')] * cols for _ in range(rows)]

    # Multi-source BFS from all 0s
    for r in range(rows):
        for c in range(cols):
            if mat[r][c] == 0:
                result[r][c] = 0
                queue.append((r, c))

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

    while queue:
        r, c = queue.popleft()

        for dr, dc in directions:
            nr, nc = r + dr, c + dc

            if 0 <= nr < rows and 0 <= nc < cols:
                if result[nr][nc] > result[r][c] + 1:
                    result[nr][nc] = result[r][c] + 1
                    queue.append((nr, nc))

    return result

Multi-Source BFS

Start BFS from multiple sources simultaneously by adding them all to the queue initially. The distance to each cell is the distance from the nearest source.

Problem: Walls and Gates

Fill each empty room with distance to the nearest gate:

INF = 2147483647

Before:                After:
INF  -1  0  INF       3  -1   0   1
INF INF INF  -1   →   2   2   1  -1
INF  -1 INF  -1       1  -1   2  -1
  0  -1 INF INF       0  -1   3   4
def walls_and_gates(rooms):
    if not rooms:
        return

    INF = 2147483647
    rows, cols = len(rooms), len(rooms[0])
    queue = deque()

    # Start from all gates
    for r in range(rows):
        for c in range(cols):
            if rooms[r][c] == 0:
                queue.append((r, c))

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

    while queue:
        r, c = queue.popleft()

        for dr, dc in directions:
            nr, nc = r + dr, c + dc

            if 0 <= nr < rows and 0 <= nc < cols:
                if rooms[nr][nc] == INF:
                    rooms[nr][nc] = rooms[r][c] + 1
                    queue.append((nr, nc))

Problem: Minimum Knight Moves

Minimum moves for a knight to reach a target:

def min_knight_moves(x, y):
    # Knight can move to 8 positions
    moves = [
        (2, 1), (2, -1), (-2, 1), (-2, -1),
        (1, 2), (1, -2), (-1, 2), (-1, -2)
    ]

    # Use absolute values (symmetric)
    x, y = abs(x), abs(y)

    queue = deque([(0, 0, 0)])  # (row, col, steps)
    visited = {(0, 0)}

    while queue:
        r, c, steps = queue.popleft()

        if r == x and c == y:
            return steps

        for dr, dc in moves:
            nr, nc = r + dr, c + dc

            # Stay within reasonable bounds
            if (nr, nc) not in visited and nr >= -2 and nc >= -2:
                visited.add((nr, nc))
                queue.append((nr, nc, steps + 1))

    return -1

Level-by-Level BFS

Process all nodes at current level before moving to next:

def bfs_by_level(grid, start):
    queue = deque([start])
    visited = {start}
    level = 0

    while queue:
        level_size = len(queue)

        for _ in range(level_size):
            r, c = queue.popleft()

            # Process cell at current level

            for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                nr, nc = r + dr, c + dc
                if is_valid(nr, nc) and (nr, nc) not in visited:
                    visited.add((nr, nc))
                    queue.append((nr, nc))

        level += 1  # Move to next level

Time and Space Complexity

  • Time: O(rows × cols) — each cell visited once
  • Space: O(rows × cols) — for queue and visited set

BFS vs DFS for Grids

| Aspect | BFS | DFS | |--------|-----|-----| | Shortest path | Guaranteed | Not guaranteed | | Memory | Queue (level size) | Stack (path length) | | Implementation | Iterative (queue) | Recursive or stack | | Use case | Distances, levels | Connectivity, paths |

Key Takeaways

  1. BFS explores level by level, guaranteeing shortest path
  2. Use multi-source BFS when there are multiple starting points
  3. Level-by-level processing is useful for distance calculations
  4. BFS uses more memory than DFS for wide graphs
  5. Many grid problems involving "minimum distance" use BFS

Matrix BFS is essential for shortest path problems on grids. Recognize when a problem asks for minimum distance or level-order processing—that's your cue to use BFS.

Notes & Highlights

© 2025 projectlighthouse. All rights reserved.