A clear explanation of Shortest Distance from All Buildings using BFS from each building with distance and reach accumulation.
Problem Restatement
We are given an m x n grid.
Each cell has one of three values:
| Value | Meaning |
|---|---|
0 | Empty land |
1 | Building |
2 | Obstacle |
We want to build a house on an empty land cell.
The house must be able to reach every building by moving only up, down, left, and right. Buildings and obstacles cannot be passed through.
Return the minimum total travel distance from one empty land cell to all buildings. If no empty land cell can reach all buildings, return -1. The problem statement defines 0 as passable empty land, 1 as a building, and 2 as an obstacle.
Input and Output
| Item | Meaning |
|---|---|
| Input | A grid of 0, 1, and 2 |
| Output | Minimum total distance from one empty land cell to all buildings |
| Movement | Up, down, left, right |
| Build location | Must be a 0 cell |
| Blocked cells | Cannot pass through 1 or 2 |
Function shape:
def shortestDistance(grid: list[list[int]]) -> int:
...Examples
Example:
grid = [
[1, 0, 2, 0, 1],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
]There are three buildings:
(0, 0), (0, 4), (2, 2)The best empty land cell is:
(1, 2)Distances:
| Building | Distance to (1, 2) |
|---|---|
(0, 0) | 3 |
(0, 4) | 3 |
(2, 2) | 1 |
Total:
3 + 3 + 1 = 7Output:
7First Thought: BFS From Every Empty Land
One direct method is:
- For every empty land cell.
- Run BFS from that cell.
- Find the distance to every building.
- Sum the distances.
- Keep the minimum valid sum.
This is correct, but it may repeat too much work.
If there are many empty cells, we run many BFS searches.
In the worst case, this costs roughly:
O(E * m * n)where E is the number of empty cells.
We can do better by reversing the direction.
Key Insight
Instead of BFS from every empty land cell, run BFS from every building.
For each building, BFS computes the shortest distance from that building to every reachable empty land cell.
We maintain two matrices:
| Matrix | Meaning |
|---|---|
dist[r][c] | Total distance from all processed buildings to this empty cell |
reach[r][c] | Number of buildings that can reach this empty cell |
After BFS from every building, an empty cell is valid only if:
reach[r][c] == total_buildingsAmong those valid cells, choose the smallest dist[r][c].
This works because shortest path distance in an unweighted grid is exactly what BFS computes.
BFS From One Building
For one building at (start_r, start_c):
- Start BFS from the building.
- Move only through empty land cells.
- Track distance by BFS layers.
- For every reached empty cell:
- Add the distance to
dist[r][c]. - Increment
reach[r][c].
- Add the distance to
We do not move through buildings or obstacles.
Algorithm
- Count all buildings.
- Create
distandreachmatrices filled with zero. - For each building:
- Run BFS.
- Update
distandreachfor all reachable empty cells.
- Scan every cell:
- It must be empty land.
- It must be reachable from every building.
- Use its total distance as a candidate answer.
- Return the minimum candidate.
- If no candidate exists, return
-1.
Correctness
BFS from a building visits empty cells in increasing distance order. Since every move has cost 1, the first time BFS reaches an empty cell, that distance is the shortest distance from the building to that cell.
The algorithm runs this BFS once for every building. Therefore, for each empty cell, dist[r][c] accumulates the shortest distances from all buildings that can reach it, and reach[r][c] counts how many buildings can reach it.
An empty cell is a valid house location exactly when every building can reach it. This is equivalent to:
reach[r][c] == total_buildingsFor each valid empty cell, dist[r][c] is the total travel distance from that cell to all buildings. The algorithm returns the minimum such value.
Therefore, the returned answer is exactly the shortest possible total travel distance. If no empty cell is reachable from all buildings, no valid candidate is found and the algorithm correctly returns -1.
Complexity
Let:
| Symbol | Meaning |
|---|---|
m | Number of rows |
n | Number of columns |
B | Number of buildings |
| Metric | Value | Why |
|---|---|---|
| Time | O(Bmn) | Each building runs one BFS over the grid |
| Space | O(mn) | dist, reach, visited, and queue |
Implementation
from collections import deque
class Solution:
def shortestDistance(self, grid: list[list[int]]) -> int:
m = len(grid)
n = len(grid[0])
dist = [[0] * n for _ in range(m)]
reach = [[0] * n for _ in range(m)]
buildings = []
for r in range(m):
for c in range(n):
if grid[r][c] == 1:
buildings.append((r, c))
directions = [
(1, 0),
(-1, 0),
(0, 1),
(0, -1),
]
def bfs(start_r: int, start_c: int) -> None:
queue = deque([(start_r, start_c, 0)])
visited = [[False] * n for _ in range(m)]
visited[start_r][start_c] = True
while queue:
r, c, d = queue.popleft()
for dr, dc in directions:
nr = r + dr
nc = c + dc
if not (0 <= nr < m and 0 <= nc < n):
continue
if visited[nr][nc]:
continue
if grid[nr][nc] != 0:
continue
visited[nr][nc] = True
dist[nr][nc] += d + 1
reach[nr][nc] += 1
queue.append((nr, nc, d + 1))
for r, c in buildings:
bfs(r, c)
total_buildings = len(buildings)
answer = float("inf")
for r in range(m):
for c in range(n):
if grid[r][c] != 0:
continue
if reach[r][c] != total_buildings:
continue
answer = min(answer, dist[r][c])
if answer == float("inf"):
return -1
return answerCode Explanation
We store the grid dimensions:
m = len(grid)
n = len(grid[0])dist accumulates total distances.
dist = [[0] * n for _ in range(m)]reach counts how many buildings can reach each empty cell.
reach = [[0] * n for _ in range(m)]Then we collect all buildings.
buildings = []
for r in range(m):
for c in range(n):
if grid[r][c] == 1:
buildings.append((r, c))The BFS starts from one building.
queue = deque([(start_r, start_c, 0)])Each queue entry stores row, column, and distance from the building.
Each BFS needs its own visited matrix.
visited = [[False] * n for _ in range(m)]When BFS sees a neighbor, it first checks boundaries.
if not (0 <= nr < m and 0 <= nc < n):
continueThen it rejects already visited cells.
if visited[nr][nc]:
continueThen it rejects buildings and obstacles.
if grid[nr][nc] != 0:
continueOnly empty land cells can be visited.
For a valid empty cell, update the distance and reach count.
dist[nr][nc] += d + 1
reach[nr][nc] += 1After running BFS from every building, scan all empty cells.
if reach[r][c] != total_buildings:
continueThis keeps only cells reachable from every building.
Then choose the smallest total distance.
answer = min(answer, dist[r][c])If no valid empty land was found, return -1.
Testing
def run_tests():
s = Solution()
assert s.shortestDistance([
[1, 0, 2, 0, 1],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
]) == 7
assert s.shortestDistance([
[1, 0],
]) == 1
assert s.shortestDistance([
[1],
]) == -1
assert s.shortestDistance([
[1, 2, 0],
]) == -1
assert s.shortestDistance([
[1, 0, 1],
]) == 2
assert s.shortestDistance([
[1, 0, 0],
[2, 2, 0],
[1, 0, 0],
]) == 6
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Standard example | Multiple buildings and obstacle |
[1, 0] | One building, one empty land |
[[1]] | No place to build |
| Building blocked by obstacle | No reachable empty land from all buildings |
| Two buildings with one middle land | Simple minimum |
| Maze-like grid | Checks path distance around blockers |