diff --git a/problems/2812-find-the-safest-path-in-a-grid/analysis.md b/problems/2812-find-the-safest-path-in-a-grid/analysis.md new file mode 100644 index 0000000..b88ad35 --- /dev/null +++ b/problems/2812-find-the-safest-path-in-a-grid/analysis.md @@ -0,0 +1,56 @@ +# 2812. Find the Safest Path in a Grid + +[LeetCode Link](https://leetcode.com/problems/find-the-safest-path-in-a-grid/) + +Difficulty: Medium +Topics: Array, Binary Search, Breadth-First Search, Union-Find, Heap (Priority Queue), Matrix +Acceptance Rate: 50.6% + +## Hints + +### Hint 1 + +This is really two problems stitched together. First, notice that the "safeness factor" of a path depends only on how close each cell gets to the *nearest* thief. So before you worry about paths at all, ask: for every cell in the grid, how far is it from the closest thief? Think about which traversal computes shortest distances from *multiple* starting points at once. + +### Hint 2 + +Once you have, for each cell, its Manhattan distance to the nearest thief, you can precompute it with a **multi-source BFS**: seed a queue with *all* thief cells at distance 0 and let the wave expand outward. In a 4-connected grid, the number of BFS steps to reach a cell equals its Manhattan distance to the nearest source — so a single BFS fills in the whole distance grid in O(n²). + +### Hint 3 + +Now the real question: among all paths from `(0,0)` to `(n-1,n-1)`, you want the one whose *minimum* cell-distance is as *large* as possible. This is a classic **maximin / widest-path** problem. Two clean ways to solve it: (a) a Dijkstra-style walk with a **max-heap**, always expanding the cell reachable with the largest bottleneck value; or (b) **binary search** on the answer `k` combined with a BFS/DSU that only walks through cells with distance `≥ k`. Both give the same result. + +## Approach + +**Step 1 — Distance-to-nearest-thief grid (multi-source BFS).** +Create a `dist` grid initialized to `-1`. Push every thief cell into a queue with `dist = 0`. Run BFS: when you pop a cell, relax its four neighbors, setting their distance to `current + 1` the first time they're visited. Because every source starts at distance 0 simultaneously, the value that lands on each cell is exactly the Manhattan distance to the *closest* thief. This costs O(n²) time. + +**Step 2 — Widest path via a max-heap (Dijkstra variant).** +We want to maximize the minimum `dist` value encountered along a path. Define `safe[r][c]` = the best achievable path-safeness when arriving at `(r, c)`. Start at `(0,0)` with `safe[0][0] = dist[0][0]` and push it onto a max-heap keyed by safeness. + +Repeatedly pop the cell with the **largest** current safeness (this greedy choice is what makes Dijkstra correct here). For each neighbor, the safeness of extending the path is `min(current safeness, dist[neighbor])`. If that value improves the neighbor's recorded `safe`, update it and push it. The first time we pop `(n-1, n-1)`, its safeness is the answer — greedy pop order guarantees we've found its optimal bottleneck. + +**Why it works.** A path's safeness is a *bottleneck* (minimum along the path), and the max-heap always finalizes the cell with the current best bottleneck first, exactly like Dijkstra finalizes the shortest-distance node first. Substituting `min` for `+` and "max-heap" for "min-heap" turns shortest-path into widest-path. + +**Worked example.** For `grid = [[0,0,1],[0,0,0],[0,0,0]]` the only thief is at `(0,2)`. The `dist` grid becomes: + +``` +2 1 0 +3 2 1 +4 3 2 +``` + +Starting at `(0,0)` with safeness 2, we can route down and along cells whose distance never drops below 2, reaching `(2,2)` with safeness 2 — the answer. + +## Complexity Analysis + +Time Complexity: O(n² log n) — the BFS is O(n²); the heap walk visits each of the n² cells and performs O(log n²) = O(log n) heap operations per push. +Space Complexity: O(n²) — the `dist` and `safe` grids plus the heap, all bounded by the number of cells. + +## Edge Cases + +- **Start or destination is a thief** (`grid[0][0] == 1` or `grid[n-1][n-1] == 1`): `dist` at that cell is 0, so every path through it has safeness 0 — the answer is 0. Example 1 is exactly this case. +- **Every cell is a thief**: all distances are 0, answer is 0. +- **`n == 1`** (single cell): the start *is* the destination; the answer is `dist[0][0]`, which is 0 since the lone cell must contain the sole thief (the constraint guarantees at least one thief). +- **Sparse thieves in a large grid**: distances grow large; make sure the heap comparison maximizes safeness and that `safe` values are only overwritten when strictly improved, to avoid redundant work and infinite loops. +- **Don't forget** cells containing thieves are still walkable — you may pass through a thief cell, it just forces the path safeness to 0. diff --git a/problems/2812-find-the-safest-path-in-a-grid/problem.md b/problems/2812-find-the-safest-path-in-a-grid/problem.md new file mode 100644 index 0000000..a95bd2f --- /dev/null +++ b/problems/2812-find-the-safest-path-in-a-grid/problem.md @@ -0,0 +1,85 @@ +--- +number: "2812" +frontend_id: "2812" +title: "Find the Safest Path in a Grid" +slug: "find-the-safest-path-in-a-grid" +difficulty: "Medium" +topics: + - "Array" + - "Binary Search" + - "Breadth-First Search" + - "Union-Find" + - "Heap (Priority Queue)" + - "Matrix" +acceptance_rate: 5061.4 +is_premium: false +created_at: "2026-07-01T05:03:26.509843+00:00" +fetched_at: "2026-07-01T05:03:26.509843+00:00" +link: "https://leetcode.com/problems/find-the-safest-path-in-a-grid/" +date: "2026-07-01" +--- + +# 2812. Find the Safest Path in a Grid + +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:** + +![](https://assets.leetcode.com/uploads/2023/07/02/example1.png) + + + **Input:** 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:** + +![](https://assets.leetcode.com/uploads/2023/07/02/example2.png) + + + **Input:** 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. + It can be shown that there are no other paths with a higher safeness factor. + + +**Example 3:** + +![](https://assets.leetcode.com/uploads/2023/07/02/example3.png) + + + **Input:** grid = [[0,0,0,1],[0,0,0,0],[0,0,0,0],[1,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, 3) is cell (1, 2). The distance between them is | 0 - 1 | + | 3 - 2 | = 2. + - The closest cell of the path to the thief at cell (3, 0) is cell (3, 2). The distance between them is | 3 - 3 | + | 0 - 2 | = 2. + It can be shown that there are no other paths with a higher safeness factor. + + + + +**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`. diff --git a/problems/2812-find-the-safest-path-in-a-grid/solution.go b/problems/2812-find-the-safest-path-in-a-grid/solution.go new file mode 100644 index 0000000..b819507 --- /dev/null +++ b/problems/2812-find-the-safest-path-in-a-grid/solution.go @@ -0,0 +1,115 @@ +package main + +import "container/heap" + +// maximumSafenessFactor returns the maximum safeness factor of any path from +// (0,0) to (n-1,n-1), where the safeness factor of a path is the minimum +// Manhattan distance from any cell on the path to the nearest thief. +// +// Approach (two phases): +// 1. Multi-source BFS from every thief cell computes, for each cell, its +// Manhattan distance to the nearest thief. In a 4-connected grid the BFS +// step count equals that Manhattan distance. +// 2. A Dijkstra-style walk with a max-heap finds the "widest" path: we always +// expand the reachable cell with the largest bottleneck value, where a +// path's value is the minimum dist over its cells. The first time we pop +// the destination, its recorded value is the answer. +func maximumSafenessFactor(grid [][]int) int { + n := len(grid) + if n == 0 { + return 0 + } + + // Phase 1: distance to nearest thief via multi-source BFS. + dist := make([][]int, n) + for i := range dist { + dist[i] = make([]int, n) + for j := range dist[i] { + dist[i][j] = -1 + } + } + + type cell struct{ r, c int } + queue := make([]cell, 0, n*n) + for r := 0; r < n; r++ { + for c := 0; c < n; c++ { + if grid[r][c] == 1 { + dist[r][c] = 0 + queue = append(queue, cell{r, c}) + } + } + } + + dirs := [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} + for len(queue) > 0 { + cur := queue[0] + queue = queue[1:] + for _, d := range dirs { + nr, nc := cur.r+d[0], cur.c+d[1] + if nr >= 0 && nr < n && nc >= 0 && nc < n && dist[nr][nc] == -1 { + dist[nr][nc] = dist[cur.r][cur.c] + 1 + queue = append(queue, cell{nr, nc}) + } + } + } + + // Phase 2: widest path via a max-heap keyed by path safeness. + safe := make([][]int, n) + for i := range safe { + safe[i] = make([]int, n) + for j := range safe[i] { + safe[i][j] = -1 + } + } + + pq := &maxHeap{{dist[0][0], 0, 0}} + heap.Init(pq) + safe[0][0] = dist[0][0] + + for pq.Len() > 0 { + item := heap.Pop(pq).(hnode) + if item.r == n-1 && item.c == n-1 { + return item.val + } + // Skip stale heap entries superseded by a better path. + if item.val < safe[item.r][item.c] { + continue + } + for _, d := range dirs { + nr, nc := item.r+d[0], item.c+d[1] + if nr < 0 || nr >= n || nc < 0 || nc >= n { + continue + } + nv := item.val + if dist[nr][nc] < nv { + nv = dist[nr][nc] + } + if nv > safe[nr][nc] { + safe[nr][nc] = nv + heap.Push(pq, hnode{nv, nr, nc}) + } + } + } + + return 0 +} + +// hnode is a heap entry: val is the path safeness to reach cell (r, c). +type hnode struct{ val, r, c int } + +// maxHeap orders entries so the largest safeness is popped first. +type maxHeap []hnode + +func (h maxHeap) Len() int { return len(h) } +func (h maxHeap) Less(i, j int) bool { return h[i].val > h[j].val } +func (h maxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h *maxHeap) Push(x any) { *h = append(*h, x.(hnode)) } + +func (h *maxHeap) Pop() any { + old := *h + n := len(old) + item := old[n-1] + *h = old[:n-1] + return item +} diff --git a/problems/2812-find-the-safest-path-in-a-grid/solution_test.go b/problems/2812-find-the-safest-path-in-a-grid/solution_test.go new file mode 100644 index 0000000..c8adf74 --- /dev/null +++ b/problems/2812-find-the-safest-path-in-a-grid/solution_test.go @@ -0,0 +1,56 @@ +package main + +import "testing" + +func TestSolution(t *testing.T) { + tests := []struct { + name string + grid [][]int + expected int + }{ + { + name: "example 1: thieves block both corners", + grid: [][]int{{1, 0, 0}, {0, 0, 0}, {0, 0, 1}}, + expected: 0, + }, + { + name: "example 2: single thief top-right", + grid: [][]int{{0, 0, 1}, {0, 0, 0}, {0, 0, 0}}, + expected: 2, + }, + { + name: "example 3: thieves on opposite corners of 4x4", + grid: [][]int{{0, 0, 0, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 0, 0, 0}}, + expected: 2, + }, + { + name: "edge case: single cell that is a thief", + grid: [][]int{{1}}, + expected: 0, + }, + { + name: "edge case: every cell is a thief", + grid: [][]int{{1, 1}, {1, 1}}, + expected: 0, + }, + { + name: "edge case: start cell is a thief", + grid: [][]int{{1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + expected: 0, + }, + { + name: "edge case: single thief in the center of 5x5", + grid: [][]int{{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}}, + expected: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := maximumSafenessFactor(tt.grid) + if result != tt.expected { + t.Errorf("maximumSafenessFactor(%v) = %d, want %d", tt.grid, result, tt.expected) + } + }) + } +}