Matrix and Graph Interview Patterns: Island Counting, Shortest Path, and Cycle Detection (2025)

Matrix as a Graph

A 2D matrix is an implicit graph: each cell (r, c) is a node; edges connect adjacent cells (up, down, left, right — 4-directional, or 8-directional including diagonals). You do not need to build an explicit adjacency list. The grid itself encodes the graph. Key operations: BFS for shortest path, DFS for connected components (islands), visited matrix to avoid revisiting cells. Most matrix problems reduce to one of these three patterns.

Pattern 1: Island Counting (DFS/BFS Flood Fill)

Count connected components of 1s in a binary grid (LC 200). DFS approach: iterate over all cells. On encountering an unvisited 1: start DFS, mark all reachable 1s as visited (set to 0 or use a visited set), increment count.

def num_islands(grid):
    m, n = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        if r = m or c = n or grid[r][c] != '1':
            return
        grid[r][c] = '0'  # mark visited in-place
        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
            dfs(r+dr, c+dc)

    for r in range(m):
        for c in range(n):
            if grid[r][c] == '1':
                dfs(r, c)
                count += 1
    return count

Time: O(m*n). Space: O(m*n) recursion stack in worst case (long snake-shaped island). BFS variant: use a queue instead of recursion — avoids stack overflow for very large grids. Union-Find variant: for each 1-cell, union with its right and down neighbors. Count distinct roots at the end. Union-Find is slower here but useful when you need to also answer “are these two cells in the same island” queries dynamically.

Pattern 2: Shortest Path in Matrix (BFS)

Find the shortest path from source to target in a grid with obstacles (LC 1091: shortest path in binary matrix). BFS guarantees shortest path in unweighted graphs. Each BFS level = distance + 1.

from collections import deque

def shortest_path(grid):
    n = len(grid)
    if grid[0][0] == 1 or grid[n-1][n-1] == 1:
        return -1
    q = deque([(0, 0, 1)])  # (row, col, distance)
    grid[0][0] = 1  # mark visited
    while q:
        r, c, dist = q.popleft()
        if r == n-1 and c == n-1:
            return dist
        for dr in [-1, 0, 1]:
            for dc in [-1, 0, 1]:
                if dr == 0 and dc == 0: continue
                nr, nc = r+dr, c+dc
                if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
                    grid[nr][nc] = 1  # mark visited
                    q.append((nr, nc, dist+1))
    return -1

Key insight: mark cells as visited when enqueuing (not when dequeuing). Marking on dequeue allows duplicate enqueues that waste memory and time. Dijkstra for weighted grids: when cells have different traversal costs, use a min-heap (heapq) instead of a deque. Push (cost, r, c); skip if already visited with lower cost.

Pattern 3: Cycle Detection in Directed Graph

Detect a cycle in a directed graph (LC 207: Course Schedule). DFS with three states: UNVISITED (0), IN_PROGRESS (1), DONE (2). A cycle exists if we reach an IN_PROGRESS node during DFS.

def can_finish(num_courses, prerequisites):
    adj = [[] for _ in range(num_courses)]
    for a, b in prerequisites:
        adj[b].append(a)

    state = [0] * num_courses  # 0=unvisited, 1=in-progress, 2=done

    def dfs(node):
        if state[node] == 1: return False  # cycle
        if state[node] == 2: return True   # already processed
        state[node] = 1
        for nei in adj[node]:
            if not dfs(nei): return False
        state[node] = 2
        return True

    return all(dfs(i) for i in range(num_courses))

Topological sort (Kahn’s algorithm): BFS-based alternative. Compute in-degrees; start from 0-in-degree nodes. If all nodes are processed: no cycle. If some nodes remain unprocessed: cycle exists. Useful when you need the topological order, not just cycle detection.

Pattern 4: Multi-Source BFS and 0-1 BFS

Multi-source BFS: start BFS from multiple sources simultaneously (add all sources to the queue at level 0). Use case: “distance from nearest 0” (LC 542), “rotting oranges” (LC 994). The BFS naturally computes the distance from the nearest source for every cell. 0-1 BFS: edge weights are 0 or 1 (not all equal). Use a deque: push weight-0 neighbors to the front (like free moves), weight-1 neighbors to the back. O(V+E) instead of O((V+E) log V) for Dijkstra. Use case: LC 1368 (minimum cost to make all grid cells reachable with 0/1 edge flipping).

See also: Meta Interview Prep

See also: Atlassian Interview Prep

See also: Coinbase Interview Prep

Scroll to Top