Loading problems...
TL;DR: The optimal solution first computes the distance from every cell to the nearest thief using Multi-Source BFS, then uses a modified Dijkstra's Algorithm (Best-First Search) to find a path that maximizes the minimum distance encountered.
In LeetCode 2812: Find the Safest Path in a Grid, you are provided with an grid containing empty cells (0) and thieves (1). Your objective is to navigate from the top-left corner to the bottom-right corner . The quality of a path is determined by its "safeness factor," defined as the minimum Manhattan distance between any cell on that path and any thief in the grid. You must return the maximum possible safeness factor achievable.
This problem combines distance field calculation with a "bottleneck path" problem, making it a classic candidate for graph traversal optimizations.
A naive brute force approach attempts to evaluate every possible path from the start to the end to find the one with the highest safeness factor.
python# Pseudo-code for Naive Approach def brute_force(grid): # 1. Calculate distances O(N^4) dist_matrix = calculate_all_distances_naive(grid) max_safeness = 0 # 2. DFS to find all paths (Exponential) def dfs(r, c, current_path_min): if r == n-1 and c == n-1: max_safeness = max(max_safeness, current_path_min) return for neighbor in neighbors(r, c): new_min = min(current_path_min, dist_matrix[neighbor]) dfs(neighbor, new_min) dfs(0, 0, dist_matrix[0][0]) return max_safeness
Complexity Analysis: The naive distance calculation takes , which can be in the worst case. Furthermore, the number of paths in a grid is exponential relative to . With , an exponential solution will immediately result in a Time Limit Exceeded (TLE).
The solution requires two distinct phases, both relying on graph traversal principles.
Phase 1: Efficient Distance Calculation (Multi-Source BFS) The brute force method calculates distances in . However, we can calculate the distance from every cell to its nearest thief in using Multi-Source Breadth-First Search. Instead of starting a BFS from one node, we initialize the queue with all thief coordinates simultaneously. The BFS expands layer by layer; the first time a cell is visited, it is guaranteed to be reached via the shortest path from the closest thief. This creates a "distance field" grid.
Phase 2: Maximizing the Minimum Value (Modified Dijkstra) Once we have the distance grid, the problem becomes finding a path from start to end such that the minimum value observed on the path is maximized. This is a variation of the Shortest Path problem. In standard Dijkstra, we greedily pick the path with the smallest total cost. Here, we greedily pick the path with the highest bottleneck capacity.
We use a Priority Queue (Max-Heap) to store states (safeness, r, c). The safeness value represents the minimum distance encountered so far on the path from the start to (r, c). By always expanding the node with the highest current safeness, we ensure that when we reach the target, we have found the optimal path.
Visual Description: Imagine the distance grid as a terrain map where high distance values represent peaks and low values (near thieves) represent valleys. You are trying to drive from the top-left to the bottom-right while staying at the highest possible elevation. You start at your current elevation. At every step, you look at all reachable neighbors and move to the one that forces you to descend the least. A Max-Heap allows you to essentially "flood" the terrain from the highest accessible points downwards until you reach the destination.

Pre-computation (Multi-Source BFS):
dist of size with infinity (or -1).(r, c) containing a thief (grid[r][c] == 1) into a queue and set their dist to 0.dist[neighbor] = dist[current] + 1.Pathfinding (Dijkstra's Algorithm):
(0, 0) into the heap. The priority value is dist[0][0].visited matrix to avoid cycles and redundant processing.(current_safeness, r, c).(r, c) is the destination (n-1, n-1), return current_safeness.(nr, nc):
new_safe = min(current_safeness, dist[nr][nc]).(nr, nc) has not been visited, mark it as visited and push (new_safe, nr, nc) to the heap.Let's trace the algorithm on a simplified grid.
Input Grid:
0 0 1
0 0 0
0 0 0
Thief at (0, 2).
Step 1: Multi-Source BFS
[(0, 2)]. dist[0][2] = 0.(0, 2): Update neighbors (0, 1) and (1, 2) to distance 1.(0, 0), (1, 1), (2, 2) to distance 2.(1, 0), (2, 1) to distance 3.(2, 0) to distance 4.Resulting Distance Matrix:
2 1 0
3 2 1
4 3 2
Step 2: Modified Dijkstra
[(2, 0, 0)]. (Value 2 comes from dist[0][0]). Visited (0, 0).(2, 0, 0). Neighbors: (0, 1) [dist 1], (1, 0) [dist 3].
(min(2, 1), 0, 1) (1, 0, 1).(min(2, 3), 1, 0) (2, 1, 0).[(2, 1, 0), (1, 0, 1)].(2, 1, 0). Neighbors: (2, 0) [dist 4], (1, 1) [dist 2].
(min(2, 4), 2, 0) (2, 2, 0).(min(2, 2), 1, 1) (2, 1, 1).[(2, 2, 0), (2, 1, 1), (1, 0, 1)].(2, 2, 0). Neighbors: (2, 1) [dist 3].
(min(2, 3), 2, 1) (2, 2, 1).[(2, 2, 1), (2, 1, 1), (1, 0, 1)].(2, 2, 1). Neighbors: (2, 2) [dist 2].
(min(2, 2), 2, 2) (2, 2, 2).[(2, 2, 2), (2, 1, 1), (1, 0, 1)].(2, 2, 2). This is target (n-1, n-1).The correctness relies on the greedy choice property of Dijkstra's algorithm.
dist[r][c] holds the true minimum Manhattan distance to any thief because BFS finds shortest paths in unweighted graphs layer by layer.u is determined by min(safeness of path to parent, dist[u]).cpp#include <vector> #include <queue> #include <algorithm> using namespace std; class Solution { public: int maximumSafenessFactor(vector<vector<int>>& grid) { int n = grid.size(); if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return 0; // Step 1: Multi-source BFS to calculate distance to nearest thief vector<vector<int>> dist(n, vector<int>(n, -1)); queue<pair<int, int>> q; for (int r = 0; r < n; ++r) { for (int c = 0; c < n; ++c) { if (grid[r][c] == 1) { dist[r][c] = 0; q.push({r, c}); } } } int dr[] = {0, 0, 1, -1}; int dc[] = {1, -1, 0, 0}; while (!q.empty()) { auto [r, c] = q.front(); q.pop(); for (int i = 0; i < 4; ++i) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < n && nc >= 0 && nc < n && dist[nr][nc] == -1) { dist[nr][nc] = dist[r][c] + 1; q.push({nr, nc}); } } } // Step 2: Dijkstra's Algorithm (Max-Heap) to find max safeness path // Priority Queue stores {safeness, r, c} priority_queue<tuple<int, int, int>> pq; pq.push({dist[0][0], 0, 0}); // Reuse dist array to mark visited nodes by setting to -1 // (or use a separate visited array) vector<vector<bool>> visited(n, vector<bool>(n, false)); visited[0][0] = true; while (!pq.empty()) { auto [safe, r, c] = pq.top(); pq.pop(); if (r == n - 1 && c == n - 1) return safe; for (int i = 0; i < 4; ++i) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < n && nc >= 0 && nc < n && !visited[nr][nc]) { visited[nr][nc] = true; // The path safeness is limited by the smallest value seen so far int newSafe = min(safe, dist[nr][nc]); pq.push({newSafe, nr, nc}); } } } return 0; } };
javaimport java.util.*; class Solution { public int maximumSafenessFactor(List<List<Integer>> grid) { int n = grid.size(); if (grid.get(0).get(0) == 1 || grid.get(n - 1).get(n - 1) == 1) return 0; int[][] dist = new int[n][n]; for (int[] row : dist) Arrays.fill(row, -1); Queue<int[]> q = new LinkedList<>(); // Step 1: Multi-source BFS for (int r = 0; r < n; r++) { for (int c = 0; c < n; c++) { if (grid.get(r).get(c) == 1) { dist[r][c] = 0; q.offer(new int[]{r, c}); } } } int[] dr = {0, 0, 1, -1}; int[] dc = {1, -1, 0, 0}; while (!q.isEmpty()) { int[] curr = q.poll(); int r = curr[0], c = curr[1]; for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < n && nc >= 0 && nc < n && dist[nr][nc] == -1) { dist[nr][nc] = dist[r][c] + 1; q.offer(new int[]{nr, nc}); } } } // Step 2: Dijkstra's Algorithm using Max-Heap // Stores int[]{safeness, r, c} PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[0] - a[0]); pq.offer(new int[]{dist[0][0], 0, 0}); boolean[][] visited = new boolean[n][n]; visited[0][0] = true; while (!pq.isEmpty()) { int[] curr = pq.poll(); int safe = curr[0], r = curr[1], c = curr[2]; if (r == n - 1 && c == n - 1) return safe; for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < n && nc >= 0 && nc < n && !visited[nr][nc]) { visited[nr][nc] = true; int newSafe = Math.min(safe, dist[nr][nc]); pq.offer(new int[]{newSafe, nr, nc}); } } } return 0; } }
pythonimport heapq from collections import deque class Solution: def maximumSafenessFactor(self, grid: List[List[int]]) -> int: n = len(grid) if grid[0][0] == 1 or grid[n-1][n-1] == 1: return 0 # Step 1: Multi-source BFS to calculate distances dist = [[-1] * n for _ in range(n)] q = deque() for r in range(n): for c in range(n): if grid[r][c] == 1: dist[r][c] = 0 q.append((r, c)) dr = [0, 0, 1, -1] dc = [1, -1, 0, 0] while q: r, c = q.popleft() for i in range(4): nr, nc = r + dr[i], c + dc[i] if 0 <= nr < n and 0 <= nc < n and dist[nr][nc] == -1: dist[nr][nc] = dist[r][c] + 1 q.append((nr, nc)) # Step 2: Dijkstra's Algorithm (Max-Heap) # Python's heapq is a min-heap, so we store negative safeness pq = [(-dist[0][0], 0, 0)] visited = set() visited.add((0, 0)) while pq: safe, r, c = heapq.heappop(pq) safe = -safe # Convert back to positive if r == n - 1 and c == n - 1: return safe for i in range(4): nr, nc = r + dr[i], c + dc[i] if 0 <= nr < n and 0 <= nc < n and (nr, nc) not in visited: visited.add((nr, nc)) new_safe = min(safe, dist[nr][nc]) heapq.heappush(pq, (-new_safe, nr, nc)) return 0
Let be the side length of the grid. Total cells = .
Time Complexity:
Space Complexity:
dist matrix of size .visited matrix of size .The combination of grid traversal and Dijkstra's algorithm is a powerful pattern for "bottleneck" or "weighted path" problems.
Why does my naive BFS/DFS approach get TLE?
Why is my Dijkstra returning the wrong answer?
Why do I need visited in Dijkstra if I already have dist?
dist in the BFS phase, Dijkstra needs to track which nodes have been finalized in the pathfinding phase to avoid cycles and redundant processing.