Walls and Gates
1. Problem & Core Objective
You are given an m × n grid where each cell is one of:
-1— a wall or obstacle0— a gateINF(2^31 − 1) — an empty room
Fill each empty room with the distance to its nearest gate. If no gate can reach it, leave it as INF.
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 4Constraints: 1 <= m, n <= 250 · movement is four-directional
What's actually being tested: multi-source BFS. Running a BFS from every room is O((m·n)²); seeding the queue with all gates at once computes every distance in a single O(m·n) sweep. It's the most valuable single idea in this section and it reappears in question 5.
2. First-Principles Thought Process
The wrong direction first
The natural reading is "for each room, find the nearest gate" — which means a BFS from each room. With up to 62,500 cells each running a 62,500-cell BFS, that's 4 × 10^9 operations.
Reverse it
Instead ask: from each gate, how far does it reach? Now the sources are gates, which are typically far fewer than rooms.
But running a separate BFS per gate and taking the minimum is still O(gates · m · n).
Seed them all simultaneously
Put every gate in the queue before starting. All of them sit at distance 0 at the same time, so the BFS expands outward from all gates in lockstep.
Why the first arrival is already the minimum
BFS visits cells in non-decreasing order of distance from the seed set. So when a cell is first dequeued — or first reached — that distance is its minimum over all gates.
No cell ever needs updating, which is why a visited cell can simply be skipped rather than compared. That's the property that makes it a single sweep rather than a relaxation algorithm like Dijkstra.
The elegant visited marker
An empty room is INF; once assigned a real distance it is no longer INF. So:
if (grid[nr][nc] != INF) continue; // wall, gate, or already assignedOne condition covers all three cases — walls are -1, gates are 0, assigned rooms are finite. No separate visited array.
Same trick as sinking cells in question 2, and it works for the same reason: the three cases genuinely warrant identical treatment.
3. Solution Paths
Approach 1 — BFS from every room (brute force)
public void wallsAndGates(int[][] rooms) {
int m = rooms.length, n = rooms[0].length;
for (int r = 0; r < m; r++)
for (int c = 0; c < n; c++)
if (rooms[r][c] == Integer.MAX_VALUE)
rooms[r][c] = bfsToNearestGate(rooms, r, c); // a full BFS per room
}- Time
O((m · n)²)· SpaceO(m · n)per BFS
Counter-questions on this approach
⭐ "How bad is that at these constraints?"
250 × 250 = 62,500 cells, and a BFS from each can visit all 62,500 — about
4 × 10^9operations. Far too slow.And most of that work is repeated: adjacent rooms explore nearly identical regions and rediscover the same distances independently.
⭐ "Reversing to 'BFS from each gate' — does that fix it?"
It helps when gates are few, but the bound is
O(gates · m · n), and a grid could be mostly gates. It also requires taking a minimum across runs, so every cell is written repeatedly.The real fix is doing one BFS with all gates seeded, which is
O(m · n)regardless of how many gates there are.
"Is there any case where per-room BFS is acceptable?"
If there were exactly one room to answer for — a single query rather than filling the grid. Then one BFS is
O(m·n)and seeding everything would be wasted. The multi-source version pays off precisely because every cell needs an answer.
Approach 2 — Multi-source BFS (optimal)
private static final int[][] DIRS = {{1,0},{-1,0},{0,1},{0,-1}};
private static final int INF = Integer.MAX_VALUE;
public void wallsAndGates(int[][] rooms) {
int m = rooms.length, n = rooms[0].length;
Queue<int[]> q = new ArrayDeque<>();
for (int r = 0; r < m; r++) // seed EVERY gate
for (int c = 0; c < n; c++)
if (rooms[r][c] == 0) q.offer(new int[]{r, c});
while (!q.isEmpty()) {
int[] cell = q.poll();
int r = cell[0], c = cell[1];
for (int[] d : DIRS) {
int nr = r + d[0], nc = c + d[1];
if (nr < 0 || nr >= m || nc < 0 || nc >= n) continue;
if (rooms[nr][nc] != INF) continue; // wall, gate, or already assigned
rooms[nr][nc] = rooms[r][c] + 1; // assign on discovery
q.offer(new int[]{nr, nc});
}
}
}Trace — the example grid, first two rounds:
| Round | Queue contents | Assigned |
|---|---|---|
| seed | (0,2), (3,0) — both gates | — |
| 1 | process (0,2) | (0,3)=1, (1,2)=1 |
| 1 | process (3,0) | (2,0)=1 |
| 2 | process (0,3), (1,2), (2,0) | (1,1)=2, (1,0)=2 … |
Every cell is written once, at its true minimum distance.
- Time
O(m · n)· SpaceO(m · n)queue worst case
Counter-questions on this approach
⭐ "Why does seeding all gates give the NEAREST gate rather than an arbitrary one?"
Because BFS expands strictly in order of distance from the seed set. Every gate is at distance 0 simultaneously, so the wavefront at step
kis exactly the set of cells whose nearest gate isksteps away.A cell is therefore first reached at its minimum distance over all gates. No later path can be shorter, which is why I never have to compare or update — first write wins, and it's already optimal.
⭐ "Why assign the distance on discovery rather than when the cell is dequeued?"
Two reasons. It doubles as the visited marker — an assigned cell is no longer
INF, so the guard rejects it. And it prevents the same cell being enqueued from several neighbours before being processed, which would inflate the queue with duplicates.Marking on enqueue is the standard BFS discipline; here it also is the answer being written.
⭐ "Why does one guard cover walls, gates, and assigned rooms?"
Because only
INFcells need processing. Walls are-1, gates are0, and an assigned room holds a finite distance — none areINF, so!= INFrejects all three.Collapsing them is safe because all three warrant the same treatment: skip. With a separate
visitedarray they'd be independent tests.
"Could rooms[r][c] + 1 overflow?"
Only if
rooms[r][c]wereINF=Integer.MAX_VALUE, which would wrap toMIN_VALUE. But a cell is only dequeued after being assigned a real distance — or being a seeded gate at 0 — so the value is always finite and small.Worth checking rather than assuming, because
MAX_VALUE + 1is exactly the kind of thing that produces a plausible-looking negative distance.
"What happens to rooms no gate can reach?"
They stay
INF, which is the required behaviour. The BFS simply never reaches them — walls block every path — so they're never written.
"How large can the queue get?"
Bounded by the frontier, but in the worst case — a grid that is all gates — every cell is seeded at once, so
O(m · n). At 62,500 entries that's fine.
Comparison
| Approach | Time | Cells written | Notes |
|---|---|---|---|
| BFS per room | O((m·n)²) | once each | 4 × 10^9 at the limit |
| BFS per gate, take min | O(gates · m·n) | repeatedly | Still multiplicative |
| Multi-source BFS | O(m·n) | once each | One sweep, first write is optimal |
4. Why the Optimal Wins
Per-room BFS repeats nearly identical explorations for adjacent rooms. Per-gate BFS repeats the grid once per gate and overwrites cells as better distances appear.
Multi-source BFS does one sweep in which every cell is written exactly once, already at its minimum. The saving isn't a clever data structure — it's asking the question from the right end, and then asking it about all sources at once.
The framing worth keeping:
When you want the distance to the NEAREST of many sources, seed the BFS queue with all of them. They all sit at distance 0, the wavefront expands from all at once, and the first time a cell is reached is already its minimum.
Question 5 is the same technique with a different story attached.
5. Java Prerequisites
Multi-source seeding
for every source cell: q.offer(new int[]{r, c}); // BEFORE the while loop
while (!q.isEmpty()) { ... }Assign on discovery, not on dequeue
rooms[nr][nc] = rooms[r][c] + 1;
q.offer(new int[]{nr, nc});The assignment is both the answer and the visited marker.
Integer.MAX_VALUE as INF — note MAX_VALUE + 1 overflows to MIN_VALUE. Safe here only because assigned cells always hold small finite values.
ArrayDeque<int[]> for the queue — offer/poll, no nulls.
6. Interview Communication Guide
Clarifying questions: Modify the grid in place (yes — the signature returns void)? Are diagonals allowed (no)? What should unreachable rooms hold (stay INF)? Can there be zero gates (yes — then nothing changes)?
The pitch
"The natural reading is 'for each room, find the nearest gate', which means a BFS per room —
O((m·n)²), about4 × 10^9here. Far too slow, and most of the work is repeated between adjacent rooms.Reversing it to 'BFS from each gate' helps but is still
O(gates · m·n).The fix is multi-source BFS: seed the queue with every gate before starting. They all sit at distance 0 simultaneously, so the wavefront expands outward from all gates at once.
That's correct because BFS visits cells in non-decreasing order of distance from the seed set. So the first time a cell is reached, that distance is already its minimum over all gates — no cell ever needs updating, which is why a visited cell can just be skipped rather than compared.
I assign the distance on discovery rather than on dequeue. That serves two purposes: it's the answer, and it's the visited marker, since an assigned room is no longer
INF. It also stops the same cell being enqueued from several neighbours.That gives one elegant guard —
if (rooms[nr][nc] != INF) continue;— which covers walls, gates, and already-assigned rooms in a single test, because none of them areINF.
O(m·n)time, every cell written exactly once. Unreachable rooms stayINFautomatically, since the BFS never gets to them."
Edge cases to volunteer:
| Input | Expected | Tests |
|---|---|---|
| No gates at all | grid unchanged | Empty seed queue — loop never runs |
| All gates | all stay 0 | Every cell seeded; nothing to assign |
| Room walled off from every gate | stays INF | Unreachable handling |
| Single cell, a gate | stays 0 | Trivial |
| Single cell, a room | stays INF | No source exists |
| 250×250 all rooms, one gate | correct distances | Deepest wavefront |
Name the no-gates case. The queue starts empty and the loop never executes, leaving the grid untouched — correct, but only if you didn't assume at least one source exists.
7. Follow-Up Questions — Modified Constraints
⭐ "Rotting Oranges — same technique?"
Yes, question 5. Rotten oranges are the sources, fresh ones are the rooms, and the answer is the number of BFS levels rather than per-cell distances. Recognising them as the same algorithm with different framing is the point of having both.
⭐ "What if moving through different cells cost different amounts?"
BFS breaks, because it assumes every edge costs 1 — the wavefront is only distance-ordered under uniform cost. You'd need Dijkstra with a priority queue, at
O(m·n log(m·n)). Multi-source works there too: seed the heap with all sources at distance 0.
"Return the distance for a single room rather than filling the grid."
Then one BFS from that room is
O(m·n)and seeding all gates is wasted work. The multi-source version pays off precisely because every cell needs an answer — worth saying, since it inverts the advice.
"What if gates could be added or removed dynamically?"
Recomputing is
O(m·n)per change. Adding a gate can be done incrementally — BFS outward from it, stopping wherever the existing distance is already smaller. Removing one is much harder, since you'd have to recompute every cell that relied on it.
"Find the room that is FARTHEST from any gate."
Same sweep, then take the maximum finite value. One extra pass, or track the maximum as cells are assigned.
"What if the grid were 10^4 × 10^4?"
10^8cells — the queue ofint[]objects would be enormous. Encode coordinates as a singleint(r * n + c) in anArrayDeque<Integer>, or better use anint[]ring buffer, to avoid allocating 100 million small arrays.