Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions problems/2812-find-the-safest-path-in-a-grid/analysis.md
Original file line number Diff line number Diff line change
@@ -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.
85 changes: 85 additions & 0 deletions problems/2812-find-the-safest-path-in-a-grid/problem.md
Original file line number Diff line number Diff line change
@@ -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`.
115 changes: 115 additions & 0 deletions problems/2812-find-the-safest-path-in-a-grid/solution.go
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 56 additions & 0 deletions problems/2812-find-the-safest-path-in-a-grid/solution_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}