LeetCode ยท #778

Swim in Rising Water Solution

You can enter a cell only once the water level reaches that cell's elevation. Return the earliest time when a path from the top-left to the bottom-right becomes possible.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

Problem #778

๐Ÿ—๏ธ

Pattern

Dijkstra minimax path

If a path visits cells with elevations [0, 2, 5, 4], then you must wait until time 5 before the entire path is swimmable. So the cost of a path is the maximum elevation on it.

Example
Input: grid = [[0,2],[1,3]] Output: 3 // You must wait until time 3 to enter the destination cell.
02
Section Two ยท Approach 1

Try All Grid Paths โ€” Exponential

A brute-force DFS can enumerate all possible routes, track the maximum elevation used on each path, and keep the minimum among them.

Why it works

The earliest feasible time is exactly the smallest possible maximum elevation over all source-to-target paths.

Problem: There are far too many paths. Once we already know a way to reach a cell with threshold t, any new route reaching that same cell with threshold โ‰ฅ t is dominated and can be ignored. Dijkstra captures that dominance naturally.
03
Section Three ยท Approach 2

Grid Dijkstra with Max Elevation State โ€” O(nยฒ log n)

Use a min-heap storing the current minimum known threshold to reach each cell. If you move into neighbor (nr,nc), the threshold becomes max(currentTime, grid[nr][nc]). This is the same minimax Dijkstra pattern as LC 1631, but the edge cost is simply the destination cell's elevation.

๐Ÿ’ก Mental model: Water level rises globally over time. Dijkstra asks: what is the earliest time each frontier cell could possibly become reachable if we choose the best route so far? We always expand the currently most promising reachable cell first.
  • Start time is already at least grid[0][0].
  • Maintain best[r][c] = smallest threshold found so far for that cell.
  • Pop the smallest threshold from the heap.
  • Relax neighbors with next = max(current, grid[nr][nc]).
  • The first time the destination is popped, its threshold is final.
Alternative view:
  • This can also be solved with binary search on time plus BFS feasibility.
  • Dijkstra is often simpler because it avoids a second search layer and directly constructs the answer.
04
Section Four ยท Trace

Visual Walkthrough

Trace [[0,2],[1,3]].

Swim in Rising Water โ€” Dijkstra threshold expansion
Start at (0,0) with threshold max(0, grid[0][0]) = 0. Neighbors: (1,0) gets threshold 1, (0,1) gets threshold 2. Best route to destination requires entering cell 3, so threshold becomes 3. answer = 3
05
Section Five ยท Implementation

Code โ€” Java & Python

Java โ€” Dijkstra threshold search
import java.util.*; class Solution { private static final int[][] DIRS = {{1,0}, {-1,0}, {0,1}, {0,-1}}; public int swimInWater(int[][] grid) { int n = grid.length; int[][] best = new int[n][n]; for (int[] row : best) Arrays.fill(row, Integer.MAX_VALUE); best[0][0] = grid[0][0]; PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]); pq.offer(new int[] { grid[0][0], 0, 0 }); // time, row, col while (!pq.isEmpty()) { int[] cur = pq.poll(); int time = cur[0], r = cur[1], c = cur[2]; if (time != best[r][c]) continue; if (r == n - 1 && c == n - 1) return time; for (int[] d : DIRS) { int nr = r + d[0], nc = c + d[1]; if (nr < 0 || nr >= n || nc < 0 || nc >= n) continue; int nextTime = Math.max(time, grid[nr][nc]); if (nextTime < best[nr][nc]) { best[nr][nc] = nextTime; pq.offer(new int[] { nextTime, nr, nc }); } } } return -1; } }
Python โ€” minimax grid path
import heapq class Solution: def swimInWater(self, grid: list[list[int]]) -> int: n = len(grid) best = [[float('inf')] * n for _ in range(n)] best[0][0] = grid[0][0] heap = [(grid[0][0], 0, 0)] dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)] while heap: time, r, c = heapq.heappop(heap) if time != best[r][c]: continue if r == n - 1 and c == n - 1: return time for dr, dc in dirs: nr, nc = r + dr, c + dc if 0 <= nr < n and 0 <= nc < n: next_time = max(time, grid[nr][nc]) if next_time < best[nr][nc]: best[nr][nc] = next_time heapq.heappush(heap, (next_time, nr, nc)) return -1
06
Section Six ยท Analysis

Complexity Analysis

ApproachTimeSpaceTrade-off
DFS over all routesExponentialO(nยฒ) recursionToo many possible paths.
Dijkstra โ† optimalO(nยฒ log n)O(nยฒ)Each cell tracks the earliest global water level needed to reach it.
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseExpected behaviorWhy it matters
Single cell gridReturn its elevationYou start there, so must wait for that cell to be swimmable.
Destination has highest valueAnswer is at least that valueYou cannot enter the end cell before its elevation is underwater.
Route with low sum but one huge peakMay be worseObjective is maximum elevation, not sum.
Heap stale entriesSkip themMultiple candidate thresholds per cell are normal.
Using plain BFSWrong answerCells do not all have equal traversal cost.
โš  Common Mistake: Returning the number of steps in the path. This is not a shortest-step problem at all. The answer is the smallest water level that makes some path possible, which equals the minimum possible maximum elevation along a path.

โ† Back to Shortest Path