Multi-source BFS
“For every cell, how far is the nearest zero?” invites a BFS from each cell, which is and hopeless. Reverse the question and it collapses:
Instead of searching from each cell to the nearest source, push every source into the queue at once and let one BFS expand outward from all of them simultaneously.
The first time any wave reaches a cell, it arrived from the nearest source — because BFS expands in non-decreasing distance order and all sources started at distance 0. One pass, , no repeated work.
This is one of those patterns that feels like a trick the first time and obvious forever after.
What you’ll learn
- Why seeding many sources still yields correct shortest distances.
- The level loop that converts BFS layers into elapsed time or distance.
- The two ways to track visited cells — and the “distance array doubles as visited” idiom.
- How to detect unreachable cells, which is what most of these problems actually test.
- Three real LeetCode problems solved in the browser: 994, 542, 1162.
The cue
Template 1 — distance to the nearest source
The distance array doubles as the visited marker, which removes a whole category of bugs.
from collections import deque
def nearest_source_distance(grid, is_source):
rows, cols = len(grid), len(grid[0])
dist = [[-1] * cols for _ in range(rows)] # -1 means "not yet reached"
queue = deque()
for r in range(rows): # SEED every source at once
for c in range(cols):
if is_source(grid[r][c]):
dist[r][c] = 0
queue.append((r, c))
while queue:
r, c = queue.popleft()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and dist[nr][nc] == -1:
dist[nr][nc] = dist[r][c] + 1 # set distance = mark visited
queue.append((nr, nc))
return distfrom collections import deque
def nearest_source_distance(grid, is_source):
rows, cols = len(grid), len(grid[0])
dist = [[-1] * cols for _ in range(rows)] # -1 means "not yet reached"
queue = deque()
for r in range(rows): # SEED every source at once
for c in range(cols):
if is_source(grid[r][c]):
dist[r][c] = 0
queue.append((r, c))
while queue:
r, c = queue.popleft()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and dist[nr][nc] == -1:
dist[nr][nc] = dist[r][c] + 1 # set distance = mark visited
queue.append((nr, nc))
return distTemplate 2 — elapsed time, via level loops
When the answer is “how many minutes/steps”, process the queue one level at a time, exactly as in Tree BFS.
steps = 0
while queue and still_work_remaining:
steps += 1
for _ in range(len(queue)): # snapshot: exactly this level
r, c = queue.popleft()
# ... expand neighbours, appending to the same queuesteps = 0
while queue and still_work_remaining:
steps += 1
for _ in range(len(queue)): # snapshot: exactly this level
r, c = queue.popleft()
# ... expand neighbours, appending to the same queueThe for _ in range(len(queue))for _ in range(len(queue)) snapshot is what separates one time step from
the next. Without it every cell would be its own “minute”.
Detecting unreachable cells
This is what most of these problems really test. After the BFS finishes, any cell still holding the initial sentinel was never reached:
- 994 Rotting Oranges — a fresh orange remains: return
-1-1. - 542 01 Matrix — guaranteed at least one zero, so everything is reachable.
- 1162 As Far from Land as Possible — all-water or all-land: return
-1-1.
Rather than rescanning the grid at the end, it is usually cleaner to keep a
counter of the remaining work (freshfresh in LC 994) and decrement it as you
convert cells. Then fresh == 0fresh == 0 is an check instead of an
sweep — and it also lets you stop the loop early.
| Time | Space | |
|---|---|---|
| Multi-source BFS | ||
| BFS once per source |
The variant map
| Variant | Sources | Answer read from | Canonical problem |
|---|---|---|---|
| Time for full spread | All rotten cells | The level count, plus a reachability check | 994 Rotting Oranges |
| Distance to nearest source | All zeros | The whole distdist grid | 542 01 Matrix |
| Maximise distance from sources | All land cells | The last level reached | 1162 |
| Fill from all gates | All gates | The distdist grid | 286 (Premium) |
| Single source, 8 directions | One cell | Level count | 1091 |
| Spread with obstacles | All sources | Same, skipping walls | 286 · 2101 |
Practice — real LeetCode problems
LC 994 — Rotting Oranges · Medium
Problem. In a grid, 00 is empty, 11 is a fresh orange, 22 is rotten.
Each minute, every fresh orange 4-directionally adjacent to a rotten one
becomes rotten. Return the minimum minutes until no fresh orange remains, or
-1-1 if that is impossible.
Constraints. 1 <= m, n <= 101 <= m, n <= 10, cells are 00, 11 or 22.
Examples. [[2,1,1],[1,1,0],[0,1,1]][[2,1,1],[1,1,0],[0,1,1]] gives 44 ·
[[2,1,1],[0,1,1],[1,0,1]][[2,1,1],[0,1,1],[1,0,1]] gives -1-1 (the bottom-left 11 is isolated) ·
[[0,2]][[0,2]] gives 00
Editorial — approach, complexity, follow-ups
All rotten oranges start rotting simultaneously, which is precisely multi-source BFS. Each BFS level is one minute.
Time . Space for the queue.
Three details, each with a test case:
fresh == 0fresh == 0up front returns00.[[0,2]][[0,2]]and[[0]][[0]]both have nothing to rot. Without this guard the level loop never runs,minutesminutesstays00, and you happen to get the right answer — but the explicit guard is what makes[[0]][[0]](no rotten oranges either) obviously correct rather than accidentally so.while queue and freshwhile queue and fresh. Stopping as soon asfreshfreshhits00avoids counting a final, wasted minute in which nothing rots. Using justwhile queuewhile queuewould over-count by one on some grids, because the last level enqueued has no fresh neighbours left to convert.-1-1when fresh oranges survive.[[1]][[1]]has a fresh orange and no rotten one, so it is immediately impossible.
The freshfresh counter is doing real work here: it gives an termination
test and an reachability check, replacing a final rescan.
Follow-ups you should expect: “Which orange rots last?” — record the cell when you set the final level. “8-directional spread?” — extend the direction list. “Oranges rot at different rates?” — edges are no longer uniform, so it becomes Dijkstra. “Why not DFS?” — DFS does not visit in distance order, so levels would be meaningless.
LC 542 — 01 Matrix · Medium
Problem. Given an m x nm x n binary matrix, return a matrix of the same shape
where each entry is the distance to the nearest 00, measured in
4-directional steps.
Constraints. 1 <= m, n <= 10^41 <= m, n <= 10^4, m * n <= 10^4m * n <= 10^4, and there is at least
one 00.
Examples. [[0,0,0],[0,1,0],[0,0,0]][[0,0,0],[0,1,0],[0,0,0]] gives itself unchanged ·
[[0,0,0],[0,1,0],[1,1,1]][[0,0,0],[0,1,0],[1,1,1]] gives [[0,0,0],[0,1,0],[1,2,1]][[0,0,0],[0,1,0],[1,2,1]]
Editorial — approach, complexity, follow-ups
Reverse the question. Rather than asking each 11 to find its nearest 00,
start from all the zeros and let the wave assign distances as it spreads.
Time . Space .
The elegant part is that distdist initialised to -1-1 serves as both the answer
and the visited set. One array, two jobs, and no way to enqueue a cell twice.
Writing distances into matmat in place is possible but fiddly, since 00 and
11 are meaningful input values and you would need a sentinel that cannot
collide. A separate distdist grid is clearer and the space is either way.
Follow-ups you should expect: “What if there were no zeros?” — the
constraints guarantee one; otherwise every cell stays -1-1 and you would
define the answer. “Diagonal distance?” — add the diagonal offsets, giving
Chebyshev distance. “Do it with extra space?” — the two-pass DP.
“Nearest 00 by Manhattan distance ignoring walls?” — no traversal needed,
just coordinate arithmetic.
LC 1162 — As Far from Land as Possible · Medium
Problem. Given an n x nn x n grid of 00 (water) and 11 (land), find the
water cell whose distance to the nearest land cell is maximised, and
return that distance (Manhattan / 4-directional). If no water or no land
exists, return -1-1.
Constraints. 1 <= n <= 1001 <= n <= 100, cells are 00 or 11.
Examples. [[1,0,1],[0,0,0],[1,0,1]][[1,0,1],[0,0,0],[1,0,1]] gives 22 (the centre) ·
[[1,0,0],[0,0,0],[0,0,0]][[1,0,0],[0,0,0],[0,0,0]] gives 44 (the far corner) ·
[[0,0],[0,0]][[0,0],[0,0]] gives -1-1
Editorial — approach, complexity, follow-ups
Maximising the distance to the nearest land sounds like an optimisation over water cells, but multi-source BFS answers it directly: seed all land, expand, and the last level reached is the farthest any water cell can be.
Time . Space .
Two details worth getting right:
- Initialise
dist = -1dist = -1. The first iteration of the loop processes the land cells themselves and incrementsdistdistto00, correctly recording that land is at distance 0 from land. Starting at00over-counts by one. - Both degenerate cases return
-1-1.not queuenot queuecatches all-water;len(queue) == n * nlen(queue) == n * ncatches all-land. Checking only one of them fails half the edge tests, and[[0,0],[0,0]][[0,0],[0,0]]/[[1,1],[1,1]][[1,1],[1,1]]cover both.
Note this is the mirror of LC 542. There the answer was the whole distance grid; here it is a single number, the maximum — which BFS hands you for free as the final level, with no need to store distances at all.
Follow-ups you should expect: “Which cell is farthest?” — record a cell when you enter the final level. “Chebyshev distance instead of Manhattan?” — use 8 directions. “Multiple such cells?” — collect all cells in the last level. “Do it without BFS?” — the two-pass DP from LC 542 computes the distance grid, then take its maximum.
LeetCode problem set
| # | Problem | Difficulty | The twist |
|---|---|---|---|
| 994 | Rotting Oranges | Medium | Levels are minutes; a freshfresh counter gives termination and reachability |
| 542 | 01 Matrix | Medium | The distdist grid doubles as the visited set; a two-pass DP also works |
| 1162 | As Far from Land as Possible | Medium | The answer is the last level; guard all-land and all-water |
| 1091 | Shortest Path in Binary Matrix | Medium | Single source, but 8 directions — plain BFS |
| 286 | Walls and Gates | Medium · Premium | Seed all gates, skip walls — the canonical statement of this pattern |
Interview follow-ups
| They ask | What they’re checking | The answer |
|---|---|---|
| “Why is seeding all sources correct?” | Whether you understand BFS’s invariant | It is BFS from a virtual super-source joined to every source by a 0-cost edge; the queue stays ordered by distance, so first visit is shortest |
| “Why mark visited on enqueue?” | The critical habit | Otherwise several neighbours enqueue the same cell before it is processed, bloating the queue and risking a later, larger distance |
| “How do you know when it is impossible?” | Rigour | Keep a counter of remaining work; a non-zero counter afterwards means unreachable cells |
| “Why not DFS?” | Deliberate choice | DFS does not visit in distance order, so levels and shortest distances are meaningless |
| “What if edges had different costs?” | The boundary | Multi-source BFS needs uniform cost; otherwise use Dijkstra with all sources pushed at distance 0 |
| “Can you do LC 542 in space?” | Breadth | Yes — the two-pass DP sweeping top-left then bottom-right |
“Why dist = -1dist = -1 initially in 1162?” | Attention to off-by-one | The first level processed is the land itself, which must register as distance 0 |
Edge-case checklist
- No sources at all — LC 994 with only fresh oranges gives
-1-1; LC 1162 with no land gives-1-1. - No targets at all — LC 994 with no fresh oranges gives
00; LC 1162 with no water gives-1-1. Both degenerate cases need guards. - Everything is a source — the queue starts full and no level ever expands.
- Unreachable cells — an isolated fresh orange behind empty cells; the reason for the counter check.
- 1×1 grid — every variant must survive it.
- Off-by-one in the level counter — initialise
-1-1when the source level counts as distance 0, and00when counting elapsed steps. - One wasted final minute (LC 994) —
while queue and freshwhile queue and freshrather thanwhile queuewhile queue. - Duplicate enqueues — guaranteed if you mark on dequeue instead of enqueue.
Recap
- Seed the queue with every source and run one BFS. Correct because it is BFS from a virtual super-source, so the first visit to a cell is its shortest distance.
- Turns into . If you are about to loop “BFS per source”, invert it.
- Mark visited on enqueue, never on dequeue — with many sources, duplicate enqueues are otherwise guaranteed.
- A distance array initialised to
-1-1is both the answer and the visited set. - For elapsed time, process one level per step with a
len(queue)len(queue)snapshot. - Track remaining work with a counter for termination and unreachability checks.
- Requires uniform edge cost; weighted versions are Dijkstra (with all sources pushed at distance 0).
Next: Union-Find Problem Patterns — the other way to answer connectivity questions, and the one that handles edges arriving over time.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
