Facebook Pixel

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 freely
  • 1: A building that you cannot pass through
  • 2: 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:

  1. We need to find shortest paths in an unweighted grid
  2. We need to calculate distances from each building to all reachable empty lands
  3. BFS guarantees finding the shortest path in terms of number of moves
  4. We can run BFS from each building to compute distances to all empty cells efficiently
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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.

  2. 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 far
    • cnt[i][j]: How many buildings can reach this cell
  3. 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!

  4. Handling unreachable cells: Some empty cells might be blocked from certain buildings by obstacles. That's why we track cnt[i][j]. Only cells where cnt[i][j] == total_buildings are valid locations (they can reach all buildings).

  5. 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 grid
  • cnt: 2D array tracking how many buildings can reach each empty cell
  • dist: 2D array accumulating the total distance from all buildings to each empty cell
  • q: 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 remains inf, 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 Evaluator

Example 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

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

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 and cnt[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 cell
  • dist matrix: O(m * n) to store cumulative distances from all buildings
  • vis set: O(m * n) in the worst case when BFS visits all cells
  • q deque: O(m * n) in the worst case when all cells are in the queue
  • The vis set and q 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More