Walls and Gates

Medium BFS

Problem

Given a 2D grid rooms representing rooms, gates, and walls, fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, leave it as INF.

  • m == rooms.length
  • n == rooms[i].length
  • 1 ≤ m, n ≤ 250
  • rooms[i][j] is −1, 0, or 2147483647

Example

Input: rooms = [[2147483647, -1, 0, 2147483647],[2147483647, 2147483647, 2147483647, -1],[2147483647, -1, 2147483647, -1],[0, -1, 2147483647, 2147483647]]
Output: [[3, -1, 0, 1],[2, 2, 1, -1],[1, -1, 2, -1],[0, -1, 3, 4]]

The algorithm starts by locating all gates (cells with value 0) and enqueues their positions. It then performs a breadth-first search (BFS) from all gates simultaneously, propagating distance values outward. Each empty room is updated with the shortest distance to any gate encountered during the BFS. Walls (-1) are never updated or traversed. The critical moment is when the BFS expands from multiple gates in parallel, ensuring the shortest distance is assigned to each room without redundant searches.

Approach

Straightforward Solution

A naive approach would run BFS or DFS from each gate separately to update distances, resulting in O(m*n*k) time where k is the number of gates, which is inefficient for large grids.

Core Observation

The shortest distance from any empty room to the nearest gate can be found by simultaneously exploring all gates outward in waves. This is a classic shortest path problem on a grid with uniform edge weights, naturally suited for BFS.

Path to Optimal

The key insight is to treat all gates as sources in a single multi-source BFS. By enqueuing all gates initially and expanding outward in layers, each empty room is assigned the shortest distance to the closest gate in a single pass. This avoids redundant searches and ensures optimal time complexity.

Optimal Approach

Initialize a queue with all gate positions and a visited set to track processed cells. Perform BFS layer by layer, updating the distance for each empty room when first visited. Skip walls and already visited cells. This guarantees the shortest distance is assigned to each room efficiently.

Time

O(m * n)

Each cell is enqueued and processed at most once during BFS. Since the grid has m rows and n columns, the total operations are proportional to m*n.

Space

O(m * n)

The queue and visited set can hold up to all cells in the grid in the worst case, resulting in O(m*n) auxiliary space.

Pattern Spotlight

BFS (Multi-Source Traversal)

When multiple sources influence shortest paths simultaneously, enqueue all sources at once and perform BFS to propagate distances in waves, ensuring each node is assigned the shortest distance from any source without redundant searches.

Solution

1import collections
2
3class Solution:
4 def wallsAndGates(self, rooms: list[list[int]]) -> None:
5 ROWS, COLS = len(rooms), len(rooms[0])
6 visit = set()
7 q = collections.deque()
8
9 for r in range(ROWS):
10 for c in range(COLS):
11 if rooms[r][c] == 0:
12 q.append([r, c])
13 visit.add((r, c))
14
15 dist = 0
16 while q:
17 for i in range(len(q)):
18 r, c = q.popleft()
19 rooms[r][c] = dist
20
21 add_room(r + 1, c)
22 add_room(r - 1, c)
23 add_room(r, c + 1)
24 add_room(r, c - 1)
25 dist += 1
26
27 def add_room(r, c):
28 if (r < 0 or r == ROWS or c < 0 or c == COLS or
29 (r, c) in visit or rooms[r][c] == -1):
30 return
31 visit.add((r, c))
32 q.append([r, c])

Step-by-Step Solution

1

Collect All Gates as BFS Starting Points

5ROWS, COLS = len(rooms), len(rooms[0])
6visit = set()
7q = collections.deque()
9for r in range(ROWS):
10 for c in range(COLS):
11 if rooms[r][c] == 0:
12 q.append([r, c])
13 visit.add((r, c))

Objective

To identify all gates in the grid and enqueue their positions as starting points for BFS.

Key Insight

By scanning the entire grid once and enqueuing all gates, the algorithm sets up a multi-source BFS. This simultaneous start from all gates allows distances to propagate outward in waves, ensuring that each empty room is assigned the shortest distance to any gate efficiently.

Interview Quick-Check

Core Logic

Enqueuing all gates initially transforms the problem into a multi-source BFS, enabling shortest distance propagation from all gates in parallel.

State & Boundaries

The visited set prevents revisiting cells and infinite loops during BFS.

Common Pitfalls & Bugs

Forgetting to mark gates as visited when enqueuing them can cause redundant processing or incorrect distance updates.

2

Perform BFS to Update Distances Layer by Layer

15dist = 0
16while q:
17 for i in range(len(q)):
18 r, c = q.popleft()
19 rooms[r][c] = dist
25 dist += 1

Objective

To propagate distance values from gates to empty rooms by processing the BFS queue in layers, updating each room's distance with the current BFS level.

Key Insight

BFS ensures that when a room is first visited, it is reached via the shortest path from the nearest gate. Processing the queue layer by layer corresponds to increasing distance values. The distance variable tracks the current BFS level, which is assigned to rooms as they are dequeued. This approach guarantees correctness and efficiency.

Interview Quick-Check

Core Logic

Processing BFS in layers with a distance counter ensures each room is assigned the shortest distance from the nearest gate.

State & Boundaries

The loop iterates until the queue is empty, meaning all reachable rooms have been processed.

Common Pitfalls & Bugs

Not updating the distance correctly or updating rooms multiple times can lead to incorrect results.

3

Expand BFS Frontier by Adding Valid Neighboring Rooms

27def add_room(r, c):
28 if (r < 0 or r == ROWS or c < 0 or c == COLS or
29 (r, c) in visit or rooms[r][c] == -1):
30 return
31 visit.add((r, c))
32 q.append([r, c])

Objective

To add valid neighboring cells to the BFS queue if they are within bounds, not walls, and not yet visited.

Key Insight

The helper function encapsulates boundary checks and visitation logic, ensuring only valid empty rooms are enqueued for future BFS processing. This modular approach prevents code duplication and maintains correctness by filtering out invalid or already processed cells.

Interview Quick-Check

Core Logic

The add_room function enforces grid boundaries, avoids walls, and prevents revisiting cells, maintaining BFS correctness and efficiency.

Common Pitfalls & Bugs

Failing to check all conditions or forgetting to mark cells as visited can cause infinite loops or incorrect distance assignments.

Line Analysis

This solution has 6 Critical lines interviewers watch for.

Line 11 Critical
if rooms[r][c] == 0:

Check if the current cell is a gate (value 0).

Identifying gates is critical because BFS starts from these cells to propagate distances.

Line 12 Critical
q.append([r, c])

Add the gate's coordinates to the BFS queue.

Enqueuing all gates initially sets up the multi-source BFS, enabling simultaneous distance propagation.

Line 19 Critical
rooms[r][c] = dist

Update the current cell's distance in the grid.

Assigning the BFS layer number as the distance ensures the shortest distance from the nearest gate is recorded.

Line 28 Critical
if (r < 0 or r == ROWS or c < 0 or c == COLS or

Check if the neighbor is out of grid bounds.

Boundary checks prevent invalid memory access and ensure BFS only processes valid cells.

Line 29 Critical
(r, c) in visit or rooms[r][c] == -1):

Check if the neighbor is already visited or is a wall (-1).

Avoiding walls and revisited cells prevents BFS from traversing invalid or redundant paths, maintaining correctness and efficiency.

Line 31 Critical
visit.add((r, c))

Mark the neighbor as visited.

Marking neighbors as visited upon enqueueing prevents multiple insertions and infinite loops.

Line 6 Core
visit = set()

Initialize a set to track visited cells.

The visited set prevents revisiting cells during BFS, which avoids infinite loops and redundant processing.

Line 13 Core
visit.add((r, c))

Mark the gate as visited.

Marking gates as visited prevents them from being reprocessed, ensuring BFS correctness and efficiency.

Line 18 Core
r, c = q.popleft()

Dequeue the next cell to process.

Dequeuing maintains BFS order, processing cells in increasing distance order from gates.

Line 30 Core
return

Return early if the neighbor is invalid.

Early exit avoids unnecessary processing and maintains BFS correctness by skipping invalid neighbors.

Line 32 Core
q.append([r, c])

Add the neighbor's coordinates to the BFS queue.

Enqueuing valid neighbors schedules them for distance assignment in subsequent BFS layers.

Line 7 Core
q = collections.deque()

Create a queue to perform BFS.

A queue is essential for BFS to process nodes in order of increasing distance from the sources.

Line 16 Core
while q:

Continue BFS while there are cells to process.

The loop ensures all reachable rooms are processed layer by layer until no new rooms remain.

Line 17 Core
for i in range(len(q)):

Process all cells in the current BFS layer.

Iterating over the current queue length ensures that all nodes at the current distance are processed before incrementing distance.

Line 25 Core
dist += 1

Increment the distance counter after processing the current BFS layer.

Increasing the distance reflects moving outward one step further from the gates in BFS traversal.

Line 27 Core
def add_room(r, c):

Define a helper function to add valid neighboring rooms to the BFS queue.

Encapsulating neighbor validation and queue insertion improves code clarity and correctness by centralizing boundary and visitation checks.

Line 15 Standard
dist = 0

Initialize the distance counter for BFS layers.

The distance variable tracks the current BFS level, corresponding to the distance from gates to rooms.

Line 9 Standard
for r in range(ROWS):

Iterate over each row in the grid.

This loop scans the entire grid to locate all gates, which are BFS starting points.

Line 10 Standard
for c in range(COLS):

Iterate over each column in the current row.

Nested iteration ensures every cell in the grid is examined for gates.

Line 5 Basic
ROWS, COLS = len(rooms), len(rooms[0])

Store the number of rows and columns in the grid.

Caching grid dimensions simplifies boundary checks and improves code readability by avoiding repeated calls to len().

Test Your Understanding

Why does starting BFS from all gates simultaneously guarantee the shortest distance to the nearest gate for each room?

Because BFS explores nodes in order of increasing distance from the sources, the first time an empty room is visited corresponds to the shortest path from the closest gate. Starting from all gates at once ensures the earliest visit is from the nearest gate.

Related Problems

BFS pattern

Don't just read it. Drill it.

Reconstruct Walls and Gates from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Walls and Gates drill

or drill a free problem