Taro Logo

Shortest Distance from All Buildings

Hard
TikTok logo
TikTok
1 view
Topics:
Graphs

You are given an m x n grid grid of values 0, 1, or 2, where:

  • 0 represents an empty land which you can pass by freely.
  • 1 represents a building which you cannot pass through.
  • 2 represents an obstacle which you cannot pass through.

You want to build a house on an empty land that reaches all buildings in the shortest amount of distance. You can only move up, down, left, and right.

Return the shortest distance for such a house. If it is not possible to build such a house according to the above rules, return -1.

The distance is the sum of the distances between the house and all the buildings.

Example 1:

Input: grid = [[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]]
Output: 7
Explanation: Building a house at (1,2) if the shortest distance since all the buildings can be reached from there.
The distance from (1,2) to the building at (0,0) is 1 + 1 = 2.
The distance from (1,2) to the building at (0,4) is 1 + 3 = 4.
The distance from (1,2) to the building at (2,2) is 1 + 0 = 1.
The total distance is 2 + 4 + 1 = 7.

Example 2:

Input: grid = [[1,0]]
Output: 1

Example 3:

Input: grid = [[1]]
Output: -1

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] is either 0, 1, or 2.
  • There will be at least one building in the grid.

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the dimensions of the grid (maximum rows and columns), and what is the range of values for grid cells (0, 1, or 2)?
  2. Can the grid be empty or contain only obstacles (all 2s), or can a solution always be found if buildings exist?
  3. If it's impossible to reach all buildings from any empty land, what value should I return?
  4. Are the buildings guaranteed to be connected, or can they be isolated?
  5. Is the input grid mutable, or should I treat it as read-only and avoid modifying it in place?

Brute Force Solution

Approach

The goal is to find the best meeting point in a city grid that minimizes travel for everyone to reach it. The brute force method explores every possible location and calculates the total travel distance from that location to all buildings.

Here's how the algorithm would work step-by-step:

  1. Consider every empty lot in the city as a potential meeting point.
  2. For each potential meeting point, figure out the shortest path from that point to every single building.
  3. Add up all those shortest path distances to get a total distance for that specific meeting point.
  4. Keep track of the total distance for each potential meeting point.
  5. Once we've looked at every empty lot, compare all the total distances we've calculated.
  6. The meeting point with the smallest total distance is the best location.

Code Implementation

def shortest_distance_from_all_buildings_brute_force(grid):
    rows = len(grid)
    cols = len(grid[0]) if rows > 0 else 0
    shortest_distance = float('inf')

    for row in range(rows):
        for col in range(cols):
            if grid[row][col] == 0:
                # Consider only empty land as starting point
                total_distance_for_point = calculate_total_distance(grid, row, col)

                if total_distance_for_point < shortest_distance:
                    shortest_distance = total_distance_for_point

    if shortest_distance == float('inf'):
        return -1
    else:
        return shortest_distance

def calculate_total_distance(grid, start_row, start_col):
    rows = len(grid)
    cols = len(grid[0]) if rows > 0 else 0
    total_distance = 0

    # Iterate through the grid to find all buildings
    for row in range(rows):
        for col in range(cols):
            if grid[row][col] == 1:
                # Calculate the shortest distance from the point to the building
                distance = bfs(grid, start_row, start_col, row, col)
                if distance == -1:
                    return float('inf')
                total_distance += distance

    return total_distance

def bfs(grid, start_row, start_col, target_row, target_col):
    rows = len(grid)
    cols = len(grid[0]) if rows > 0 else 0
    queue = [(start_row, start_col, 0)]
    visited = set()
    visited.add((start_row, start_col))

    while queue:
        row, col, distance = queue.pop(0)

        if row == target_row and col == target_col:
            return distance

        # Explore neighbors
        neighbors = [(row + 1, col), (row - 1, col), (row, col + 1), (row, col - 1)]
        for next_row, next_col in neighbors:
            if 0 <= next_row < rows and 0 <= next_col < cols and \
               grid[next_row][next_col] != 1 and (next_row, next_col) not in visited and \
               grid[next_row][next_col] == 0:
                queue.append((next_row, next_col, distance + 1))
                # Mark the cell as visited
                visited.add((next_row, next_col))

    # Return -1 if no path is found
    return -1

Big(O) Analysis

Time Complexity
O(M * N * M * N)The algorithm iterates through each cell in the grid (M rows, N columns) as a potential meeting point, which takes O(M * N). For each potential meeting point, it calculates the shortest distance to every building using, for example, Breadth-First Search (BFS). In the worst case, BFS might visit all cells in the grid again (M * N), so BFS itself takes O(M * N). Since we are iterating all cells for each possible meeting point, the total time complexity is O(M * N * M * N).
Space Complexity
O(M*N)The brute force approach, as described, explores every empty lot. For each empty lot, it calculates the shortest path to every building. This shortest path calculation likely uses a Breadth-First Search (BFS) or Depth-First Search (DFS), which requires a queue or stack (respectively) to keep track of the locations to visit, and a visited set/matrix to avoid cycles. In the worst-case scenario, the queue or stack and the visited matrix could potentially store all the cells of the grid, leading to auxiliary space proportional to the grid's dimensions, where M is the number of rows and N is the number of columns. Therefore, the space complexity is O(M*N).

Optimal Solution

Approach

The problem asks us to find the best meeting point on a map, considering how far everyone has to walk from their houses (buildings). The efficient approach involves calculating the walking distance from each building to every empty lot, then summing these distances to determine the total walking distance for each empty lot.

Here's how the algorithm would work step-by-step:

  1. For each building, use a technique to explore the map from that building outwards, calculating the distance to every reachable empty lot.
  2. Store the total walking distance from each building to all reachable empty lots.
  3. Keep a separate count of how many buildings can actually reach each empty lot.
  4. Once we have the distances from all buildings, check all empty lots to see which ones are reachable from all buildings.
  5. Of the empty lots reachable from all buildings, find the one with the minimum total walking distance.

Code Implementation

from collections import deque

def shortest_distance(grid):
    if not grid or not grid[0]:
        return -1

    rows = len(grid)
    cols = len(grid[0])
    buildings_count = 0
    total_distance = [[0] * cols for _ in range(rows)]
    reachable_buildings = [[0] * cols for _ in range(rows)]

    # Count the number of buildings.
    for row in range(rows):
        for col in range(cols):
            if grid[row][col] == 1:
                buildings_count += 1

    def bfs(start_row, start_col):
        queue = deque([(start_row, start_col, 0)])
        visited = [[False] * cols for _ in range(rows)]
        visited[start_row][start_col] = True
        distance = [[0] * cols for _ in range(rows)]

        while queue:
            row, col, dist = queue.popleft()

            for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                new_row = row + dr
                new_col = col + dc

                if 0 <= new_row < rows and 0 <= new_col < cols and \
                   grid[new_row][new_col] == 0 and not visited[new_row][new_col]:
                    visited[new_row][new_col] = True
                    distance[new_row][new_col] = dist + 1
                    total_distance[new_row][new_col] += dist + 1
                    reachable_buildings[new_row][new_col] += 1
                    queue.append((new_row, new_col, dist + 1))

    # Iterate through each building and perform BFS.
    for row in range(rows):
        for col in range(cols):
            if grid[row][col] == 1:
                bfs(row, col)

    minimum_distance = float('inf')

    # Find the minimum distance among all reachable empty land.
    for row in range(rows):
        for col in range(cols):
            # Only consider empty land reachable from all buildings.
            if grid[row][col] == 0 and reachable_buildings[row][col] == buildings_count:
                minimum_distance = min(minimum_distance, total_distance[row][col])

    if minimum_distance == float('inf'):
        return -1
    else:
        return minimum_distance

Big(O) Analysis

Time Complexity
O(m*n*(m*n))The algorithm iterates through each building in the grid (in the worst case, all '1's which is O(m*n)). For each building, a Breadth-First Search (BFS) is performed to calculate the distance to every reachable empty land (0). In the worst-case scenario, the BFS visits all the cells in the grid, resulting in O(m*n) complexity. The BFS operation also has cell visitation and queue operations. Therefore, the overall time complexity is O(m*n * m*n) where m and n are the dimensions of the grid.
Space Complexity
O(MN)The space complexity is dominated by the auxiliary space used to store the total walking distance and the reachability count for each empty lot. Specifically, the algorithm maintains a 2D array of size M x N (where M is the number of rows and N is the number of columns in the input grid) to store the accumulated distances from all buildings to each empty lot, and another 2D array of the same size to keep track of how many buildings can reach each empty lot. The BFS traversal within each building uses a queue whose maximum size can be M x N in the worst case. Therefore, the space complexity is O(MN) for storing these distance and reachability matrices and O(MN) for the queue, which simplifies to O(MN).

Edge Cases

CaseHow to Handle
Null or empty gridReturn -1 immediately as no buildings or empty land exist.
Grid with no buildings (all cells are 0 or obstacles)Return -1, since it is impossible to reach all buildings from any empty land.
Grid with no empty land (all cells are 1 or 2)Return -1, because there are no starting points from which to calculate distances.
Only one building presentAll empty land can reach the single building; find the empty land with minimal distance to the single building.
Grid with only one row or one columnBFS should still work correctly traversing in only one direction.
Large grid dimensions potentially leading to integer overflow in distance calculationsUse long data type to store distance sums to prevent overflow.
Disjoint groups of buildings, where no empty land can reach all buildingsAfter BFS from each building, check if any empty land was not visited by all buildings; return -1 if found.
Obstacles blocking all paths between buildingsBFS will not be able to reach all buildings from any empty land, resulting in every point failing the 'visited by all buildings' check, so return -1.