Find the Safest Path in a Grid

Medium
15 days ago

You are given a 0-indexed 2D matrix grid of size n x n, where (r, c) represents:

  • A cell containing a thief if grid[r][c] = 1
  • An empty cell if grid[r][c] = 0

You are initially positioned at cell (0, 0). In one move, you can move to any adjacent cell in the grid, including cells containing thieves.

The safeness factor of a path on the grid is defined as the minimum manhattan distance from any cell in the path to any thief in the grid.

Return the maximum safeness factor of all paths leading to cell (n - 1, n - 1).

An adjacent cell of cell (r, c) is one of the cells (r, c + 1), (r, c - 1), (r + 1, c), and (r - 1, c) if it exists.

The Manhattan distance between two cells (a, b) and (x, y) is equal to |a - x| + |b - y|, where |val| denotes the absolute value of val.

Example 1:

grid = [[1,0,0],[0,0,0],[0,0,1]]

Output: 0

Explanation: All paths from (0, 0) to (n - 1, n - 1) go through the thieves in cells (0, 0) and (n - 1, n - 1).

Example 2:

grid = [[0,0,1],[0,0,0],[0,0,0]]

Output: 2

Explanation: The path depicted in the picture above has a safeness factor of 2 since:

  • The closest cell of the path to the thief at cell (0, 2) is cell (0, 0). The distance between them is | 0 - 0 | + | 0 - 2 | = 2.

Constraints:

  • 1 <= grid.length == n <= 400
  • grid[i].length == n
  • grid[i][j] is either 0 or 1.
  • There is at least one thief in the grid.
Sample Answer

Let's break down how to solve this problem. The core idea is to find the maximum safeness factor, which is the minimum Manhattan distance from any cell in the path to the nearest thief. This involves a combination of graph traversal (finding paths) and distance calculation.

1. Naive (Brute Force) Approach

The most straightforward approach would be to:

  1. Find all possible paths from (0, 0) to (n-1, n-1).
  2. For each path, calculate the safeness factor.
  3. Return the maximum safeness factor among all paths.

This is highly inefficient because the number of possible paths can grow exponentially with the size of the grid.

2. Optimal Approach: Binary Search + BFS

Instead of generating all possible paths, we can use binary search to find the maximum possible safeness factor. For a given safeness factor k, we can use Breadth-First Search (BFS) to check if there exists a path from (0, 0) to (n-1, n-1) such that all cells in the path have a safeness of at least k.

Here's a detailed breakdown:

  1. Calculate Distance to Nearest Thief:

    • Use BFS to find the Manhattan distance from each cell to the nearest thief. This will give us a distance grid where distance[r][c] is the distance of cell (r, c) to the nearest thief.
  2. Binary Search:

    • Set low = 0 and high = 2 * (n - 1) (maximum possible Manhattan distance).
    • While low <= high:
      • Calculate mid = low + (high - low) // 2.
      • Check if there exists a path from (0, 0) to (n-1, n-1) with safeness factor at least mid using BFS (see below).
      • If such a path exists, then low = mid + 1 (try for a larger safeness factor).
      • Otherwise, high = mid - 1 (try for a smaller safeness factor).
  3. BFS to Check Path Existence:

    • Given a safeness factor k:
      • Start a BFS from (0, 0).
      • At each cell (r, c), only explore adjacent cells (nr, nc) if distance[nr][nc] >= k.
      • If we reach (n-1, n-1), return true (a path with safeness factor k exists).
      • If the BFS finishes without reaching (n-1, n-1), return false.

Code Implementation (Python)

from collections import deque

def maximumSafenessFactor(grid):
    n = len(grid)

    def bfs_distances():
        distances = [[float('inf')] * n for _ in range(n)]
        queue = deque()
        for r in range(n):
            for c in range(n):
                if grid[r][c] == 1:
                    distances[r][c] = 0
                    queue.append((r, c))

        while queue:
            r, c = queue.popleft()
            for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                nr, nc = r + dr, c + dc
                if 0 <= nr < n and 0 <= nc < n and distances[nr][nc] == float('inf'):
                    distances[nr][nc] = distances[r][c] + 1
                    queue.append((nr, nc))
        return distances

    distances = bfs_distances()

    def can_reach_destination(safeness):
        visited = [[False] * n for _ in range(n)]
        queue = deque([(0, 0)])
        visited[0][0] = True

        while queue:
            r, c = queue.popleft()
            if r == n - 1 and c == n - 1:
                return True

            for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                nr, nc = r + dr, c + dc
                if 0 <= nr < n and 0 <= nc < n and not visited[nr][nc] and distances[nr][nc] >= safeness:
                    visited[nr][nc] = True
                    queue.append((nr, nc))

        return False

    low, high = 0, 2 * (n - 1)
    ans = 0
    while low <= high:
        mid = low + (high - low) // 2
        if can_reach_destination(mid):
            ans = mid
            low = mid + 1
        else:
            high = mid - 1

    return ans

3. Run-time Analysis

  • Calculating distances using BFS: O(n^2), where n is the size of the grid.
  • Binary search: O(log(2n)), where n is the size of the grid.
  • BFS to check path existence for a given safeness: O(n^2).
  • Overall: O(n^2 + n^2 * log(n)) which simplifies to O(n^2 * log(n)).

4. Space Usage Analysis

  • Distance grid: O(n^2).
  • Visited grid (in BFS): O(n^2).
  • Queue (in BFS): O(n^2) in the worst case.
  • Overall: O(n^2).

5. Edge Cases

  • No Thieves: If there are no thieves, the distance to the nearest thief is infinite. The algorithm needs to handle this case (it will naturally, as the initial distances will be infinity, and binary search will return the maximum possible distance). The given problem statement says that there is at least one thief in the grid, so we don't have to worry about it.
  • Thieves Everywhere: If every cell has a thief, the maximum safeness factor will be 0. The algorithm handles this correctly.
  • Grid Size = 1: If the grid size is 1x1, the result is 0 if grid[0][0] = 1 and a large value if grid[0][0] = 0 (but problem specifies grid size > 1).
  • No Path: If there is no path from (0,0) to (n-1, n-1) with any safeness factor > 0, the algorithm returns 0.

In summary, the optimal solution uses binary search to find the maximum safeness factor, combined with BFS to efficiently check the existence of paths for a given safeness factor. The code calculates the Manhattan distance from each cell to the nearest thief and uses this information within the binary search and BFS. The time complexity is O(n^2 log n) and space complexity is O(n^2).