Multi-Source BFS - Intuition Guide¶
The Mental Model: Dropping Multiple Pebbles¶
Imagine dropping pebbles into a still pond. Each pebble creates ripples that expand outward. If you drop one pebble, you see one set of ripples expanding from that point. But what if you drop multiple pebbles simultaneously?
When multiple ripples meet, they don't create new sources - they simply merge. The key insight: every point in the pond reaches the nearest pebble first.
Multi-source BFS works exactly like this: - Each source cell is a "pebble dropped at time 0" - The BFS wavefront expands like ripples - Each cell is reached by the nearest source first
Why Multi-Source BFS?¶
The Naive Approach (Don't Do This)¶
For a problem like "find distance to nearest zero for each cell":
# BAD: O(k * m * n) where k = number of zeros
for each zero in grid:
run BFS from this zero
update all cells with min distance
This runs BFS once per source - expensive and redundant.
The Multi-Source Insight¶
# GOOD: O(m * n) regardless of source count
add ALL zeros to queue at distance 0
run ONE BFS expanding from all sources simultaneously
The magic: BFS guarantees that the first time we reach a cell, it's via the shortest path. Since all sources start at distance 0, the first source to reach any cell must be the nearest one.
Core Pattern Visualization¶
Step 0: Initialize all sources
βββββββββββββββββββββββββββββββββββ
β S = source (in queue) β
β . = target (unvisited) β
β # = obstacle β
β β
β S . . # S β
β . . . . . β
β . # . . . β
β S . . . . β
βββββββββββββββββββββββββββββββββββ
Step 1: First BFS level (distance 1)
βββββββββββββββββββββββββββββββββββ
β S 1 . # S β
β 1 . . . 1 β
β . # . . . β
β S 1 . . . β
βββββββββββββββββββββββββββββββββββ
Step 2: Second BFS level (distance 2)
βββββββββββββββββββββββββββββββββββ
β S 1 2 # S β
β 1 2 . 2 1 β
β 2 # . . 2 β
β S 1 2 . . β
βββββββββββββββββββββββββββββββββββ
... and so on until all cells are reached
The Three Variants¶
Variant 1: Propagation Timer (Rotting Oranges)¶
Question: "How long until everything is infected?"
Mental model: Zombie infection spreading. All zombies move one step per minute. We want to know when the last survivor gets bitten.
ββββββββββββββββββββββββββββββββββββββββββ
β Minute 0: Initial state β
β π§ π π (2 fresh, 1 zombie) β
β β
β Minute 1: β
β π§ π§ π (1 fresh, 2 zombies) β
β β
β Minute 2: β
β π§ π§ π§ (0 fresh, all zombies)β
β β
β Answer: 2 minutes β
ββββββββββββββββββββββββββββββββββββββββββ
Key implementation detail: Count BFS levels. Return levels - 1 because we count one extra level after the last conversion.
Variant 2: Distance Fill (Walls and Gates)¶
Question: "What's the distance from each room to the nearest exit?"
Mental model: Evacuation signs. Each room needs a sign showing distance to the nearest exit. Instead of measuring from each room (expensive), measure from each exit (cheap with multi-source BFS).
ββββββββββββββββββββββββββββββββββββββββββ
β Before: After: β
β β # 0 β 3 # 0 1 β
β β β β # β 2 2 1 # β
β β # β # 1 # 2 # β
β 0 # β β 0 # 3 4 β
ββββββββββββββββββββββββββββββββββββββββββ
Key implementation detail: Store distance[neighbor] = distance[current] + 1. The grid cell itself becomes the distance tracker.
Variant 3: Distance Field (01 Matrix)¶
Question: "For each cell, what's the distance to the nearest special cell?"
Mental model: Computing a "heat map" of distances. Every zero is a heat source at temperature 0. Heat radiates outward, increasing by 1 at each step.
ββββββββββββββββββββββββββββββββββββββββββ
β Input: Output: β
β 0 0 0 0 0 0 β
β 0 1 0 β 0 1 0 β
β 1 1 1 1 2 1 β
ββββββββββββββββββββββββββββββββββββββββββ
Key implementation detail: Can modify in-place or create separate distance matrix.
Common Mistakes and Fixes¶
Mistake 1: BFS from Each Source Separately¶
# WRONG: O(k * m * n)
for source in sources:
bfs_from(source)
update_min_distances()
# RIGHT: O(m * n)
queue = deque(all_sources)
bfs_from_queue()
Mistake 2: Wrong Initialization¶
# WRONG: Adding sources with distance 1
for source in sources:
queue.append((source, 1)) # Should be 0!
# RIGHT: Sources are at distance 0
for source in sources:
queue.append((source, 0)) # Correct
dist[source] = 0
Mistake 3: Forgetting Level Counting Adjustment¶
# For propagation timer problems:
while queue:
for _ in range(len(queue)): # Process whole level
...
levels += 1
# The while loop runs one extra time after last conversion
return levels - 1 # Not levels!
Mistake 4: Double-Counting Visits¶
# WRONG: Mark visited after popping
while queue:
cell = queue.popleft()
if cell in visited:
continue
visited.add(cell) # Too late! May have added duplicates
# RIGHT: Mark visited before adding
if neighbor not in visited:
visited.add(neighbor) # Mark immediately
queue.append(neighbor)
Quick Pattern Recognition¶
| Problem Statement Contains | Pattern |
|---|---|
| "minimum time for all to become X" | Multi-source BFS (timer) |
| "distance to nearest X for each cell" | Multi-source BFS (distance field) |
| "fill each cell with distance to X" | Multi-source BFS (distance fill) |
| "spreading/infection/propagation" | Multi-source BFS (timer) |
Visual Summary¶
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Multi-Source BFS Pipeline β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββ β
β β Initialize β For each source cell: β
β β Queue β - Add to queue β
β β β - Mark as distance 0 / visited β
β ββββββββββ¬βββββββββ β
β β β
β βΌ β
β βββββββββββββββββββ β
β β BFS Loop β While queue not empty: β
β β β - Pop cell β
β β β - For each neighbor: β
β β β - If valid target: β
β β β - Update distance/state β
β β β - Add to queue β
β ββββββββββ¬βββββββββ β
β β β
β βΌ β
β βββββββββββββββββββ β
β β Return Result β Timer: max level (or -1 if unreachable) β
β β β Distance: modified grid or new matrix β
β βββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ