317. Shortest Distance from All Buildings π
Problem Description
You have an m x n
grid where each cell contains one of three values:
0
: Empty land that you can walk through freely1
: A building that you cannot pass through2
: An obstacle that you cannot pass through
Your goal is to find the best location on an empty land (a cell with value 0
) to build a house such that the total travel distance from this house to all buildings is minimized.
Movement is restricted to four directions: up, down, left, and right (no diagonal movement).
The total travel distance is calculated as the sum of the shortest path distances from your chosen location to each building in the grid. The distance between cells is measured using the Manhattan distance (number of steps needed when moving only horizontally or vertically).
Return the minimum total travel distance if it's possible to find such a location that can reach all buildings. If no valid location exists (for example, if some buildings are unreachable from any empty land), return -1
.
Example scenario: If there are 3 buildings in the grid and you choose an empty cell, you need to calculate the shortest path from that cell to each of the 3 buildings, then sum these distances. The answer would be the empty cell that gives you the smallest sum.
Key constraints:
- You can only build on empty land (cells with
0
) - The chosen location must be able to reach ALL buildings in the grid
- Obstacles and buildings block movement paths
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The grid can be viewed as a graph where each cell is a node, and adjacent cells (up, down, left, right) are connected by edges. We need to traverse this graph to find shortest paths.
Is it a tree?
- No: The grid is not a tree structure. It's a 2D grid where cells can have multiple paths between them, and there can be cycles when traversing empty lands.
Is the problem related to directed acyclic graphs?
- No: The grid allows bidirectional movement (you can move back and forth between cells), so it's not a directed acyclic graph.
Is the problem related to shortest paths?
- Yes: The core of the problem is finding the shortest path distances from potential house locations to all buildings. We need to calculate the minimum total travel distance, which is the sum of shortest paths.
Is the graph Weighted?
- No: Each move between adjacent cells has the same cost (distance of 1). All edges have equal weight, making this an unweighted graph problem.
BFS
- Yes: For unweighted shortest path problems, BFS is the optimal choice. BFS explores nodes level by level, guaranteeing the shortest path in terms of number of steps.
Conclusion: The flowchart correctly leads us to use BFS (Breadth-First Search) for this problem. BFS is perfect here because:
- We need to find shortest paths in an unweighted grid
- We need to calculate distances from each building to all reachable empty lands
- BFS guarantees finding the shortest path in terms of number of moves
- We can run BFS from each building to compute distances to all empty cells efficiently
Intuition
The key insight is to reverse our thinking. Instead of starting from each empty land and finding distances to all buildings (which would be inefficient), we start from each building and find distances to all reachable empty lands.
Think about it this way: if we have 3 buildings and 20 empty cells, it's more efficient to run BFS 3 times (once from each building) rather than 20 times (once from each empty cell).
Here's the thought process:
-
Why BFS from buildings? Each building needs to contribute its distance to every potential house location. By running BFS from each building, we can calculate how far that building is from every empty cell in one traversal.
-
Accumulating distances: As we run BFS from each building, we maintain two key pieces of information for each empty cell:
dist[i][j]
: The total sum of distances from all buildings processed so farcnt[i][j]
: How many buildings can reach this cell
-
The aggregation trick: After running BFS from all buildings, each empty cell will have accumulated the sum of distances to all buildings that can reach it. This is exactly what we need - the total travel distance!
-
Handling unreachable cells: Some empty cells might be blocked from certain buildings by obstacles. That's why we track
cnt[i][j]
. Only cells wherecnt[i][j] == total_buildings
are valid locations (they can reach all buildings). -
Finding the minimum: Once we have all the accumulated distances, we simply scan through all valid empty cells and pick the one with the minimum total distance.
The beauty of this approach is that it transforms a complex pathfinding problem into a simple accumulation problem. Each BFS adds its "layer" of distances, and at the end, we have a complete picture of the total travel distances for all potential locations.
Solution Approach
Let's walk through the implementation step by step:
1. Initialize Data Structures:
total
: Counter for the total number of buildings in the gridcnt
: 2D array tracking how many buildings can reach each empty celldist
: 2D array accumulating the total distance from all buildings to each empty cellq
: Deque for BFS traversal
2. Find All Buildings and Run BFS from Each:
for i in range(m):
for j in range(n):
if grid[i][j] == 1: # Found a building
total += 1
q.append((i, j))
# Run BFS from this building
3. BFS Implementation from Each Building:
- Start BFS from building position
(i, j)
- Use
d
to track the current distance level (starts at 0, increments with each layer) - Use
vis
set to avoid revisiting cells in the same BFS run
d = 0
vis = set()
while q:
d += 1 # Move to next distance level
for _ in range(len(q)): # Process all cells at current distance
r, c = q.popleft()
# Check all 4 directions
for a, b in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
x, y = r + a, c + b
4. Process Each Valid Empty Cell:
For each adjacent cell (x, y)
:
- Check if it's within bounds:
0 <= x < m and 0 <= y < n
- Check if it's empty land:
grid[x][y] == 0
- Check if not visited in this BFS:
(x, y) not in vis
If all conditions are met:
cnt[x][y] += 1 # One more building can reach this cell dist[x][y] += d # Add distance from current building q.append((x, y)) # Add to queue for next level vis.add((x, y)) # Mark as visited
5. Find the Optimal Location: After BFS from all buildings:
ans = inf
for i in range(m):
for j in range(n):
if grid[i][j] == 0 and cnt[i][j] == total:
ans = min(ans, dist[i][j])
Only consider empty cells where cnt[i][j] == total
(reachable from all buildings). Among these valid locations, choose the one with minimum dist[i][j]
.
6. Return Result:
- If
ans
remainsinf
, no valid location exists, return-1
- Otherwise, return
ans
(the minimum total distance)
Key Optimizations:
- Level-order BFS: Processing all nodes at the same distance level together ensures we calculate the shortest paths
- Single Pass Accumulation: Instead of storing individual distances and summing later, we accumulate as we go
- Early Validation: By tracking
cnt
, we can quickly identify which cells are valid candidates
The time complexity is O(m * n * k)
where k
is the number of buildings, as we run BFS from each building. Space complexity is O(m * n)
for the auxiliary arrays.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider this 3x3 grid:
[1, 0, 0] [0, 0, 0] [0, 0, 1]
Where 1
= building, 0
= empty land
Step 1: Initialize data structures
total = 0
(building counter)cnt = [[0,0,0], [0,0,0], [0,0,0]]
(tracks building reach)dist = [[0,0,0], [0,0,0], [0,0,0]]
(accumulates distances)
Step 2: Process first building at (0,0)
total = 1
- Run BFS from (0,0):
- Distance level 1: Reach (0,1) and (1,0)
cnt[0][1] = 1
,dist[0][1] = 1
cnt[1][0] = 1
,dist[1][0] = 1
- Distance level 2: Reach (0,2), (1,1), (2,0)
cnt[0][2] = 1
,dist[0][2] = 2
cnt[1][1] = 1
,dist[1][1] = 2
cnt[2][0] = 1
,dist[2][0] = 2
- Distance level 3: Reach (1,2), (2,1)
cnt[1][2] = 1
,dist[1][2] = 3
cnt[2][1] = 1
,dist[2][1] = 3
- Distance level 1: Reach (0,1) and (1,0)
After first building:
cnt: [[0,1,1], [1,1,1], [1,1,0]] dist: [[0,1,2], [1,2,3], [2,3,0]]
Step 3: Process second building at (2,2)
total = 2
- Run BFS from (2,2):
- Distance level 1: Reach (2,1) and (1,2)
cnt[2][1] = 2
,dist[2][1] = 3 + 1 = 4
cnt[1][2] = 2
,dist[1][2] = 3 + 1 = 4
- Distance level 2: Reach (2,0), (1,1), (0,2)
cnt[2][0] = 2
,dist[2][0] = 2 + 2 = 4
cnt[1][1] = 2
,dist[1][1] = 2 + 2 = 4
cnt[0][2] = 2
,dist[0][2] = 2 + 2 = 4
- Distance level 3: Reach (1,0), (0,1)
cnt[1][0] = 2
,dist[1][0] = 1 + 3 = 4
cnt[0][1] = 2
,dist[0][1] = 1 + 3 = 4
- Distance level 1: Reach (2,1) and (1,2)
Final state:
cnt: [[0,2,2], [2,2,2], [2,2,0]] dist: [[0,4,4], [4,4,4], [4,4,0]]
Step 4: Find optimal location
- Check all cells where
grid[i][j] == 0
andcnt[i][j] == total (2)
:- (0,1): valid, total distance = 4
- (0,2): valid, total distance = 4
- (1,0): valid, total distance = 4
- (1,1): valid, total distance = 4
- (1,2): valid, total distance = 4
- (2,0): valid, total distance = 4
- (2,1): valid, total distance = 4
Result: The minimum total distance is 4. Any of these empty cells would be optimal. The cell at (1,1) is particularly interesting as it's centrally located - it's 2 steps from each building (1β1 diagonally translates to 2 Manhattan distance steps).
This example demonstrates how:
- BFS from each building accumulates distances
- The
cnt
array ensures we only consider cells reachable from all buildings - The final
dist
values give us the total travel distance for each valid location
Solution Implementation
1from collections import deque
2from typing import List
3from math import inf
4
5class Solution:
6 def shortestDistance(self, grid: List[List[int]]) -> int:
7 # Get grid dimensions
8 rows, cols = len(grid), len(grid[0])
9
10 # Track total number of buildings
11 total_buildings = 0
12
13 # Count matrix: tracks how many buildings can reach each empty cell
14 building_reach_count = [[0] * cols for _ in range(rows)]
15
16 # Distance matrix: accumulates total distance from all buildings to each empty cell
17 total_distance = [[0] * cols for _ in range(rows)]
18
19 # Process each cell in the grid
20 for i in range(rows):
21 for j in range(cols):
22 # If current cell is a building
23 if grid[i][j] == 1:
24 total_buildings += 1
25
26 # BFS from this building to all reachable empty cells
27 queue = deque([(i, j)])
28 visited = set()
29 current_distance = 0
30
31 while queue:
32 current_distance += 1
33 # Process all cells at current distance level
34 level_size = len(queue)
35
36 for _ in range(level_size):
37 row, col = queue.popleft()
38
39 # Check all 4 directions
40 directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
41 for delta_row, delta_col in directions:
42 new_row, new_col = row + delta_row, col + delta_col
43
44 # Check if new position is valid empty cell and not visited
45 if (0 <= new_row < rows and
46 0 <= new_col < cols and
47 grid[new_row][new_col] == 0 and
48 (new_row, new_col) not in visited):
49
50 # Update count and distance for this empty cell
51 building_reach_count[new_row][new_col] += 1
52 total_distance[new_row][new_col] += current_distance
53
54 # Add to queue and mark as visited
55 queue.append((new_row, new_col))
56 visited.add((new_row, new_col))
57
58 # Find the minimum distance among all empty cells reachable by all buildings
59 min_distance = inf
60 for i in range(rows):
61 for j in range(cols):
62 # Check if cell is empty and reachable by all buildings
63 if grid[i][j] == 0 and building_reach_count[i][j] == total_buildings:
64 min_distance = min(min_distance, total_distance[i][j])
65
66 # Return result: -1 if no valid meeting point exists, otherwise minimum distance
67 return -1 if min_distance == inf else min_distance
68
1class Solution {
2 public int shortestDistance(int[][] grid) {
3 int rows = grid.length;
4 int cols = grid[0].length;
5
6 // Count total number of buildings and track distances
7 int totalBuildings = 0;
8 int[][] reachCount = new int[rows][cols]; // Number of buildings that can reach each empty cell
9 int[][] totalDistance = new int[rows][cols]; // Sum of distances from all buildings to each empty cell
10
11 // Direction vectors for moving up, right, down, left
12 int[] directions = {-1, 0, 1, 0, -1};
13
14 // Process each building in the grid
15 for (int i = 0; i < rows; i++) {
16 for (int j = 0; j < cols; j++) {
17 if (grid[i][j] == 1) { // Found a building
18 totalBuildings++;
19
20 // BFS from this building to all reachable empty spaces
21 Deque<int[]> queue = new LinkedList<>();
22 queue.offer(new int[] {i, j});
23 boolean[][] visited = new boolean[rows][cols];
24 int distance = 0;
25
26 while (!queue.isEmpty()) {
27 distance++;
28 int levelSize = queue.size();
29
30 // Process all cells at current distance level
31 for (int k = 0; k < levelSize; k++) {
32 int[] current = queue.poll();
33
34 // Check all 4 adjacent cells
35 for (int dir = 0; dir < 4; dir++) {
36 int nextRow = current[0] + directions[dir];
37 int nextCol = current[1] + directions[dir + 1];
38
39 // Validate cell: within bounds, is empty land, and not visited
40 if (nextRow >= 0 && nextRow < rows &&
41 nextCol >= 0 && nextCol < cols &&
42 grid[nextRow][nextCol] == 0 &&
43 !visited[nextRow][nextCol]) {
44
45 // Update statistics for this empty cell
46 reachCount[nextRow][nextCol]++;
47 totalDistance[nextRow][nextCol] += distance;
48
49 // Mark as visited and add to queue for next level
50 visited[nextRow][nextCol] = true;
51 queue.offer(new int[] {nextRow, nextCol});
52 }
53 }
54 }
55 }
56 }
57 }
58 }
59
60 // Find the empty cell with minimum total distance that can reach all buildings
61 int minDistance = Integer.MAX_VALUE;
62 for (int i = 0; i < rows; i++) {
63 for (int j = 0; j < cols; j++) {
64 // Check if this empty cell can reach all buildings
65 if (grid[i][j] == 0 && reachCount[i][j] == totalBuildings) {
66 minDistance = Math.min(minDistance, totalDistance[i][j]);
67 }
68 }
69 }
70
71 // Return -1 if no valid meeting point exists
72 return minDistance == Integer.MAX_VALUE ? -1 : minDistance;
73 }
74}
75
1class Solution {
2public:
3 int shortestDistance(vector<vector<int>>& grid) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Count of buildings that can reach each empty cell
8 vector<vector<int>> reachableCount(rows, vector<int>(cols));
9 // Total distance from all buildings to each empty cell
10 vector<vector<int>> totalDistance(rows, vector<int>(cols));
11
12 // Direction vectors for moving up, right, down, left
13 vector<int> directions = {-1, 0, 1, 0, -1};
14
15 int totalBuildings = 0;
16
17 // Process each building in the grid
18 for (int i = 0; i < rows; ++i) {
19 for (int j = 0; j < cols; ++j) {
20 if (grid[i][j] == 1) { // Found a building
21 ++totalBuildings;
22
23 // BFS to calculate distances from this building to all empty cells
24 queue<pair<int, int>> bfsQueue;
25 bfsQueue.push({i, j});
26 vector<vector<bool>> visited(rows, vector<bool>(cols));
27 int currentDistance = 0;
28
29 while (!bfsQueue.empty()) {
30 ++currentDistance;
31 int levelSize = bfsQueue.size();
32
33 // Process all cells at current distance level
34 for (int k = 0; k < levelSize; ++k) {
35 auto [currentRow, currentCol] = bfsQueue.front();
36 bfsQueue.pop();
37
38 // Check all 4 adjacent cells
39 for (int dir = 0; dir < 4; ++dir) {
40 int nextRow = currentRow + directions[dir];
41 int nextCol = currentCol + directions[dir + 1];
42
43 // Check if next cell is valid empty cell and not visited
44 if (nextRow >= 0 && nextRow < rows &&
45 nextCol >= 0 && nextCol < cols &&
46 grid[nextRow][nextCol] == 0 &&
47 !visited[nextRow][nextCol]) {
48
49 // Update reachable count and distance for this empty cell
50 ++reachableCount[nextRow][nextCol];
51 totalDistance[nextRow][nextCol] += currentDistance;
52 bfsQueue.push({nextRow, nextCol});
53 visited[nextRow][nextCol] = true;
54 }
55 }
56 }
57 }
58 }
59 }
60 }
61
62 // Find the minimum total distance among all valid empty cells
63 int minDistance = INT_MAX;
64 for (int i = 0; i < rows; ++i) {
65 for (int j = 0; j < cols; ++j) {
66 // Check if this empty cell can be reached by all buildings
67 if (grid[i][j] == 0 && reachableCount[i][j] == totalBuildings) {
68 minDistance = min(minDistance, totalDistance[i][j]);
69 }
70 }
71 }
72
73 // Return -1 if no valid meeting point exists
74 return minDistance == INT_MAX ? -1 : minDistance;
75 }
76};
77
1function shortestDistance(grid: number[][]): number {
2 const rows = grid.length;
3 const cols = grid[0].length;
4
5 // Count of buildings that can reach each empty cell
6 const reachableCount: number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0));
7 // Total distance from all buildings to each empty cell
8 const totalDistance: number[][] = Array(rows).fill(0).map(() => Array(cols).fill(0));
9
10 // Direction vectors for moving up, right, down, left
11 const directions = [-1, 0, 1, 0, -1];
12
13 let totalBuildings = 0;
14
15 // Process each building in the grid
16 for (let i = 0; i < rows; i++) {
17 for (let j = 0; j < cols; j++) {
18 if (grid[i][j] === 1) { // Found a building
19 totalBuildings++;
20
21 // BFS to calculate distances from this building to all empty cells
22 const bfsQueue: [number, number][] = [];
23 bfsQueue.push([i, j]);
24 const visited: boolean[][] = Array(rows).fill(false).map(() => Array(cols).fill(false));
25 let currentDistance = 0;
26
27 while (bfsQueue.length > 0) {
28 currentDistance++;
29 const levelSize = bfsQueue.length;
30
31 // Process all cells at current distance level
32 for (let k = 0; k < levelSize; k++) {
33 const [currentRow, currentCol] = bfsQueue.shift()!;
34
35 // Check all 4 adjacent cells
36 for (let dir = 0; dir < 4; dir++) {
37 const nextRow = currentRow + directions[dir];
38 const nextCol = currentCol + directions[dir + 1];
39
40 // Check if next cell is valid empty cell and not visited
41 if (nextRow >= 0 && nextRow < rows &&
42 nextCol >= 0 && nextCol < cols &&
43 grid[nextRow][nextCol] === 0 &&
44 !visited[nextRow][nextCol]) {
45
46 // Update reachable count and distance for this empty cell
47 reachableCount[nextRow][nextCol]++;
48 totalDistance[nextRow][nextCol] += currentDistance;
49 bfsQueue.push([nextRow, nextCol]);
50 visited[nextRow][nextCol] = true;
51 }
52 }
53 }
54 }
55 }
56 }
57 }
58
59 // Find the minimum total distance among all valid empty cells
60 let minDistance = Number.MAX_SAFE_INTEGER;
61 for (let i = 0; i < rows; i++) {
62 for (let j = 0; j < cols; j++) {
63 // Check if this empty cell can be reached by all buildings
64 if (grid[i][j] === 0 && reachableCount[i][j] === totalBuildings) {
65 minDistance = Math.min(minDistance, totalDistance[i][j]);
66 }
67 }
68 }
69
70 // Return -1 if no valid meeting point exists
71 return minDistance === Number.MAX_SAFE_INTEGER ? -1 : minDistance;
72}
73
Time and Space Complexity
Time Complexity: O(mΒ² * nΒ²)
The algorithm performs a BFS from each building (grid value = 1) to find distances to all reachable empty lands. In the worst case:
- There can be up to
O(m * n)
buildings in the grid - For each building, we perform a BFS that potentially visits all
O(m * n)
cells - Each cell is processed once per BFS with constant time operations
- Therefore, the overall time complexity is
O(m * n) * O(m * n) = O(mΒ² * nΒ²)
Space Complexity: O(m * n)
The space usage consists of:
cnt
matrix:O(m * n)
to track how many buildings can reach each empty celldist
matrix:O(m * n)
to store cumulative distances from all buildingsvis
set:O(m * n)
in the worst case when BFS visits all cellsq
deque:O(m * n)
in the worst case when all cells are in the queue- The
vis
set andq
deque are reused for each building's BFS, not accumulated
The total space complexity is O(m * n)
as all components scale linearly with the grid size.
Common Pitfalls
1. Visiting the Starting Building in BFS
One of the most common mistakes is accidentally adding the starting building position to the visited set or processing it as a neighbor, which can lead to incorrect distance calculations or infinite loops.
Pitfall Example:
# WRONG: Starting building gets processed as its own neighbor
queue = deque([(i, j)])
visited = set([(i, j)]) # Problem: marking building as visited
while queue:
row, col = queue.popleft()
for delta_row, delta_col in directions:
new_row, new_col = row + delta_row, col + delta_col
# The building at (i,j) might get re-added if it's adjacent to itself
Solution: The correct approach is to add the starting building to the queue but NOT to the visited set initially. Instead, only mark neighboring cells as visited:
queue = deque([(i, j)])
visited = set() # Don't add starting position
while queue:
current_distance += 1
for _ in range(len(queue)):
row, col = queue.popleft()
for delta_row, delta_col in directions:
# Only process and mark neighbors as visited
if (new_row, new_col) not in visited:
visited.add((new_row, new_col))
2. Incorrect Distance Tracking in BFS Levels
Another critical mistake is updating the distance counter at the wrong time, leading to off-by-one errors in distance calculations.
Pitfall Example:
# WRONG: Distance incremented for each cell, not each level while queue: row, col = queue.popleft() current_distance += 1 # Problem: increments for every cell for delta_row, delta_col in directions: # Distance becomes incorrect for cells at the same level
Solution: Increment distance once per BFS level, not per cell:
while queue:
current_distance += 1 # Increment once for the entire level
level_size = len(queue)
for _ in range(level_size): # Process all cells at current distance
row, col = queue.popleft()
# All cells processed in this loop are at the same distance
3. Reusing Visited Set Across Different Building BFS Runs
Using a global visited set for all buildings instead of resetting it for each building's BFS can cause cells to be incorrectly marked as unreachable.
Pitfall Example:
# WRONG: Global visited set
visited = set() # Defined outside the building loop
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1:
# Uses the same visited set, preventing cells from being
# reached by subsequent buildings
queue = deque([(i, j)])
# BFS continues with contaminated visited set
Solution: Create a fresh visited set for each building's BFS:
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1:
queue = deque([(i, j)])
visited = set() # Fresh visited set for each building
# Each building gets its own independent BFS traversal
4. Not Handling Edge Case of No Buildings or No Empty Land
The code might fail or return incorrect results when the grid contains no buildings (nothing to connect to) or no empty land (nowhere to build).
Solution: Add validation checks:
# After counting buildings and finding minimum distance if total_buildings == 0: return -1 # No buildings to connect to # The existing check for min_distance == inf handles the no empty land case return -1 if min_distance == inf else min_distance
How does merge sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!