Swim in Rising Water
Problem
n × n grid holds distinct elevations. At time t
the water level is t, and you may move (instantly, 4-directionally) between
cells whose elevations are both ≤ t. Return the earliest time at which you
can get from the top-left cell to the bottom-right cell.[[0,2],[1,3]]Output3[[0,1,2,3,4],[24,23,22,21,5],
[12,13,14,15,16],[11,17,18,19,20],
[10,9,8,7,6]]Output161 ≤ n ≤ 50 · 0 ≤ grid[i][j] < n2 · all values distinct
Try it yourself
class Solution {
public int swimInWater(int[][] grid) {
}
}
Instinct Weighted edges plus "minimum cost, time or effort to reach" - grab a min-heap and always expand the cheapest unsettled node next (Dijkstra).
Memory hack Pop the cheapest, skip the stale, relax the rest - Dijkstra is a ripple in a pond that touches near shores before far ones.
Open the Shortest Paths guide →Approaches
Brute ForceTry Every Water Level + DFS
O(n4)Space O(n2)For t = max(start, end), t+1, ... run a DFS that only enters cells
with elevation ≤ t; the first t that reaches the corner
is the answer. Up to n2 levels × O(n2) DFS.
public int swimInWater(int[][] grid) { int n = grid.length; for (int t = Math.max(grid[0][0], grid[n - 1][n - 1]); ; t++) if (reach(grid, 0, 0, t, new boolean[n][n])) return t; } private boolean reach(int[][] g, int r, int c, int t, boolean[][] seen) { int n = g.length; if (r < 0 || c < 0 || r >= n || c >= n || seen[r][c] || g[r][c] > t) return false; if (r == n - 1 && c == n - 1) return true; seen[r][c] = true; return reach(g, r + 1, c, t, seen) || reach(g, r - 1, c, t, seen) || reach(g, r, c + 1, t, seen) || reach(g, r, c - 1, t, seen); }
Obviously correct, but repeats a full flood fill for every level.
BetterBinary Search on Time + DFS
O(n2 log n)Space O(n2)Reachability is monotonic: if you can swim at time t, you
can at any later time. So binary-search the smallest feasible t in
[max(start, end), n2-1], using the same DFS as the check.
public int swimInWater(int[][] grid) { int n = grid.length; int lo = Math.max(grid[0][0], grid[n - 1][n - 1]), hi = n * n - 1; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (reach(grid, 0, 0, mid, new boolean[n][n])) hi = mid; // feasible: go lower else lo = mid + 1; } return lo; } private boolean reach(int[][] g, int r, int c, int t, boolean[][] seen) { int n = g.length; if (r < 0 || c < 0 || r >= n || c >= n || seen[r][c] || g[r][c] > t) return false; if (r == n - 1 && c == n - 1) return true; seen[r][c] = true; return reach(g, r + 1, c, t, seen) || reach(g, r - 1, c, t, seen) || reach(g, r, c + 1, t, seen) || reach(g, r, c - 1, t, seen); }
Only O(log n) flood fills, but still several full passes over the grid.
OptimalDijkstra (Min-Heap on Max Elevation)
O(n2 log n)Space O(n2)Grow the reachable region from the start, always entering the lowest frontier cell; the key of a cell is the max elevation on the path to it. The first time the corner is popped, its key is the minimum possible water level.
public int swimInWater(int[][] grid) { int n = grid.length; int[] dirs = {-1, 0, 1, 0, -1}; boolean[][] visited = new boolean[n][n]; PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]); pq.offer(new int[]{grid[0][0], 0, 0}); // {max elevation so far, r, c} while (!pq.isEmpty()) { int[] cur = pq.poll(); int t = cur[0], r = cur[1], c = cur[2]; if (visited[r][c]) continue; visited[r][c] = true; if (r == n - 1 && c == n - 1) return t; for (int d = 0; d < 4; d++) { int nr = r + dirs[d], nc = c + dirs[d + 1]; if (nr >= 0 && nr < n && nc >= 0 && nc < n && !visited[nr][nc]) pq.offer(new int[]{Math.max(t, grid[nr][nc]), nr, nc}); } } return -1; // unreachable: the grid is connected }
One pass with early exit — the standard answer for minimax paths.
0..n2-1: adding cells in
elevation order to a Union-Find until start and end connect gives
O(n2 α(n)).At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Try Every Water Level + DFS | O(n4) | O(n2) |
| Better | Binary Search on Time + DFS | O(n2 log n) | O(n2) |
| Optimal | Dijkstra (Min-Heap on Max Elevation) | O(n2 log n) | O(n2) |
- Starting the search below
grid[0][0]orgrid[n-1][n-1]. - Summing elevations (shortest path) instead of taking the max along the path.
- Returning when a cell is pushed rather than when it is popped.
- Recognise “minimise the worst step” as a minimax / bottleneck path.
- Mention the monotonic predicate ⇒ binary search on the answer.
- Compare with Path With Minimum Effort — same Dijkstra template.