Before attempting this problem, you should be comfortable with:
The safeness factor of a path is the minimum distance to any thief along that path. We want to maximize this minimum. First, we need to know the distance from every cell to the nearest thief, which we can compute using multi-source BFS from all thief positions. Then, finding the safest path becomes a modified shortest path problem where we maximize the minimum edge weight along the path, which Dijkstra's algorithm with a max-heap handles well.
minDist[r][c] for every cell.(0, 0) with priority equal to minDist[0][0].(N-1, N-1), return the current safeness factor.min(current safeness, minDist[neighbor]).class Solution:
def maximumSafenessFactor(self, grid: List[List[int]]) -> int:
N = len(grid)
def in_bounds(r, c):
return min(r, c) >= 0 and max(r, c) < N
def precompute():
q = deque()
min_dist = {}
for r in range(N):
for c in range(N):
if grid[r][c]:
q.append([r, c, 0])
min_dist[(r, c)] = 0
while q:
r, c, dist = q.popleft()
nei = [[r+1, c], [r-1, c], [r, c+1], [r, c-1]]
for r2, c2 in nei:
if in_bounds(r2, c2) and (r2, c2) not in min_dist:
min_dist[(r2, c2)] = dist + 1
q.append([r2, c2, dist + 1])
return min_dist
min_dist = precompute()
maxHeap = [(-min_dist[(0, 0)], 0, 0)] # (dist, r, c)
visit = set()
visit.add((0, 0))
while maxHeap:
dist, r, c = heapq.heappop(maxHeap)
dist = -dist
if (r, c) == (N-1, N-1):
return dist
nei = [[r+1, c], [r-1, c], [r, c+1], [r, c-1]]
for r2, c2 in nei:
if in_bounds(r2, c2) and (r2, c2) not in visit:
visit.add((r2, c2))
dist2 = min(dist, min_dist[(r2, c2)])
heapq.heappush(maxHeap, (-dist2, r2, c2))This approach is identical to the previous one but optimizes space by reusing the input grid to store the precomputed minimum distances. Additionally, it uses a safeFactor array to track the best safeness factor found so far for each cell, allowing us to skip cells if we have already found a better path to them.
0 and others to -1.safeFactor array to track the best safeness to each cell.0 with initial safeness minDist[0][0].0 if no path exists.class Solution:
def maximumSafenessFactor(self, grid):
N = len(grid)
minDist = grid
directions = [0, 1, 0, -1, 0]
q = deque()
for r in range(N):
for c in range(N):
if grid[r][c] == 1:
q.append(r * N + c)
minDist[r][c] = 0
else:
minDist[r][c] = -1
while q:
node = q.popleft()
r, c = divmod(node, N)
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
if 0 <= r2 < N and 0 <= c2 < N and minDist[r2][c2] == -1:
minDist[r2][c2] = minDist[r][c] + 1
q.append(r2 * N + c2)
maxHeap = [(-minDist[0][0], 0)]
safeFactor = [0] * (N * N)
safeFactor[0] = minDist[0][0]
while maxHeap:
dist, node = heapq.heappop(maxHeap)
dist = -dist
r, c = divmod(node, N)
if r == N - 1 and c == N - 1:
return dist
if safeFactor[node] > dist:
continue
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
node2 = r2 * N + c2
if 0 <= r2 < N and 0 <= c2 < N:
dist2 = min(dist, minDist[r2][c2])
if dist2 > safeFactor[node2]:
safeFactor[node2] = dist2
heapq.heappush(maxHeap, (-dist2, node2))
return 0Instead of using Dijkstra to find the optimal safeness, we can binary search on the answer. For a given safeness threshold, we check if there exists a path from start to end using only cells with minDist >= threshold. This check is a simple BFS or DFS. The maximum valid threshold is our answer.
minDist for all cells using multi-source BFS from thieves.[0, min(minDist[0][0], minDist[N-1][N-1])].mid:(N-1, N-1) from (0, 0) using only cells with minDist >= mid.l = mid + 1).r = mid - 1).class Solution:
def maximumSafenessFactor(self, grid):
N = len(grid)
minDist = grid
directions = [0, 1, 0, -1, 0]
q = deque()
for r in range(N):
for c in range(N):
if grid[r][c] == 1:
q.append(r * N + c)
minDist[r][c] = 0
else:
minDist[r][c] = -1
while q:
node = q.popleft()
r, c = divmod(node, N)
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
if 0 <= r2 < N and 0 <= c2 < N and minDist[r2][c2] == -1:
minDist[r2][c2] = minDist[r][c] + 1
q.append(r2 * N + c2)
def canReach(threshold):
q = deque([0])
visited = [False] * (N * N)
visited[0] = True
while q:
node = q.popleft()
r, c = divmod(node, N)
if r == N - 1 and c == N - 1:
return True
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
node2 = r2 * N + c2
if (0 <= r2 < N and 0 <= c2 < N and not visited[node2] and
minDist[r2][c2] >= threshold
):
visited[node2] = True
q.append(node2)
return False
l, r = 0, min(minDist[0][0], minDist[N - 1][N - 1])
while l <= r:
mid = (l + r) // 2
if canReach(mid):
l = mid + 1
else:
r = mid - 1
return l - 10-1 BFS is an optimization when edge weights are only 0 or 1. Here we adapt it: moving to a neighbor with the same or better safeness has cost 0, while moving to a neighbor with worse safeness has cost 1 (we are forced to accept a lower minimum). By adding zero-cost moves to the front and cost-1 moves to the back of a deque, we process cells in order of decreasing safeness factor.
minDist using multi-source BFS.safeFactor[0] = min(minDist[0][0], minDist[N-1][N-1]) and track the running result res.0.res = min(res, safeFactor[node]).min(safeFactor[current], minDist[neighbor]).res.class Solution:
def maximumSafenessFactor(self, grid):
N = len(grid)
minDist = grid
directions = [0, 1, 0, -1, 0]
q = deque()
for r in range(N):
for c in range(N):
if grid[r][c] == 1:
q.append(r * N + c)
minDist[r][c] = 0
else:
minDist[r][c] = -1
while q:
node = q.popleft()
r, c = divmod(node, N)
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
if 0 <= r2 < N and 0 <= c2 < N and minDist[r2][c2] == -1:
minDist[r2][c2] = minDist[r][c] + 1
q.append(r2 * N + c2)
safeFactor = [-1] * (N * N)
res = safeFactor[0] = min(minDist[N - 1][N - 1], minDist[0][0])
q.append(0)
while q:
node = q.popleft()
r, c = divmod(node, N)
res = min(res, safeFactor[node])
if r == N - 1 and c == N - 1:
break
for i in range(4):
r2, c2 = r + directions[i], c + directions[i + 1]
node2 = r2 * N + c2
if 0 <= r2 < N and 0 <= c2 < N and safeFactor[node2] == -1:
safeFactor[node2] = min(safeFactor[node], minDist[r2][c2])
if safeFactor[node2] < res:
q.append(node2)
else:
q.appendleft(node2)
return resTo compute the minimum distance from each cell to the nearest thief, you must start BFS from all thief cells simultaneously. A common mistake is running separate BFS from each thief and taking the minimum, which results in O(n^4) time complexity instead of O(n^2).
Since we want to maximize the minimum distance (safeness factor), we need a max-heap to always process the path with the highest current safeness first. Using a min-heap (standard Dijkstra) will find the path with the lowest safeness instead, giving incorrect results.
The safeness of any path is bounded by the minimum distance at both the start cell (0,0) and the destination cell (N-1, N-1). If either cell contains a thief (distance 0), the maximum safeness is 0 regardless of the path taken. Some solutions skip this edge case check.