You are given a 0-indexed 2D matrix grid
of size n x n
, where (r, c)
represents:
grid[r][c] = 1
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.
For example:
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).
grid = [[0,0,1],[0,0,0],[0,0,0]]
Output: 2
Explanation: The path from (0, 0) -> (0, 1) -> (0, 2) -> (1, 2) -> (2, 2) 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
.
What is the optimal algorithm to solve this problem?
The brute force approach would involve checking every possible path from (0, 0) to (n-1, n-1) and calculating the safeness factor for each path. We can use Depth-First Search (DFS) to explore all possible paths. For each path, we calculate the minimum Manhattan distance from each cell in the path to every thief in the grid. The maximum of these minimum distances, across all paths, is the answer.
def manhattan_distance(r1, c1, r2, c2):
return abs(r1 - r2) + abs(c1 - c2)
def calculate_safeness(path, thieves):
safeness = float('inf')
for r, c in path:
min_dist = float('inf')
for tr, tc in thieves:
min_dist = min(min_dist, manhattan_distance(r, c, tr, tc))
safeness = min(safeness, min_dist)
return safeness
def find_paths(grid):
n = len(grid)
thieves = []
for r in range(n):
for c in range(n):
if grid[r][c] == 1:
thieves.append((r, c))
paths = []
def dfs(r, c, current_path):
if r < 0 or r >= n or c < 0 or c >= n or (r, c) in current_path:
return
current_path.append((r, c))
if r == n - 1 and c == n - 1:
paths.append(current_path[:]) # Append a copy to avoid modification
current_path.pop()
return
dfs(r + 1, c, current_path)
dfs(r - 1, c, current_path)
dfs(r, c + 1, current_path)
dfs(r, c - 1, current_path)
current_path.pop()
dfs(0, 0, [])
return paths, thieves
def maximum_safeness_factor_brute_force(grid):
paths, thieves = find_paths(grid)
if not paths:
return 0 # No path exists
max_safeness = 0
for path in paths:
max_safeness = max(max_safeness, calculate_safeness(path, thieves))
return max_safeness
# Example usage
grid1 = [[1,0,0],[0,0,0],[0,0,1]]
print(f"Brute Force Safeness factor for grid1: {maximum_safeness_factor_brute_force(grid1)}")
grid2 = [[0,0,1],[0,0,0],[0,0,0]]
print(f"Brute Force Safeness factor for grid2: {maximum_safeness_factor_brute_force(grid2)}")
grid3 = [[0,0,0,1],[0,0,0,0],[0,0,0,0],[1,0,0,0]]
print(f"Brute Force Safeness factor for grid3: {maximum_safeness_factor_brute_force(grid3)}")
An optimized approach uses binary search to find the maximum possible safeness factor. For each potential safeness factor k
, we use Breadth-First Search (BFS) to check if there exists a path from (0, 0) to (n-1, n-1) where all cells in the path have a Manhattan distance of at least k
to the nearest thief.
Here's the breakdown:
k
(potential safeness factor):
k
to the nearest thief. If a path exists, it means k
is a feasible safeness factor, so try a higher value. Otherwise, try a lower value.from collections import deque
def maximum_safeness_factor(grid):
n = len(grid)
# Calculate distances to the nearest thief for each cell
distances = calculate_distances(grid)
# Binary search for the maximum safeness factor
left, right = 0, 2 * n - 2 # Max possible Manhattan distance
ans = 0
while left <= right:
mid = (left + right) // 2
if can_reach_destination(grid, distances, mid):
ans = mid
left = mid + 1 # Try to increase safeness factor
else:
right = mid - 1 # Decrease safeness factor
return ans
def calculate_distances(grid):
n = len(grid)
distances = [[float('inf')] * n for _ in range(n)]
queue = deque()
# Add all thieves to the queue and set their distance to 0
for r in range(n):
for c in range(n):
if grid[r][c] == 1:
distances[r][c] = 0
queue.append((r, c))
# BFS to calculate distances
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
def can_reach_destination(grid, distances, safeness):
n = len(grid)
if distances[0][0] < safeness or distances[n-1][n-1] < safeness: #optimization: cannot reach if either start or end are too close
return False
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:
queue.append((nr, nc))
visited[nr][nc] = True
return False
# Example usage
grid1 = [[1,0,0],[0,0,0],[0,0,1]]
print(f"Optimal Safeness factor for grid1: {maximum_safeness_factor(grid1)}")
grid2 = [[0,0,1],[0,0,0],[0,0,0]]
print(f"Optimal Safeness factor for grid2: {maximum_safeness_factor(grid2)}")
grid3 = [[0,0,0,1],[0,0,0,0],[0,0,0,0],[1,0,0,0]]
print(f"Optimal Safeness factor for grid3: {maximum_safeness_factor(grid3)}")
2 * (n-1)
.distances
array requires O(n^2) space.visited
array in can_reach_destination
requires O(n^2) space.paths
is empty, the function returns 0. In the optimal approach, if can_reach_destination
always returns false, the binary search will converge to 0.