#778 Swim in Rising Water
the problem
In plain words
grid is n × n, grid[r][c] is the elevation, and all values are a permutation of 0..n² − 1. At time t the water depth everywhere is t, and you can swim between adjacent cells whose elevations are both ≤ t. Return the least t at which you can swim from the top-left to the bottom-right.
in grid = [[0,2],[1,3]]
out 3
You can't reach the corner until it is underwater at t = 3.
in grid = [[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]]
out 16
- 1 ≤ n ≤ 50
- 0 ≤ grid[i][j] < n²
- All values are unique
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
LeetCode-style: complete the class. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternDijkstraBinary search on the answerUnion-Find (DSU)
The time needed for a path is its highest cell. We want the path whose highest cell is as low as possible — a minimax path, exactly like LC 1631.
solutions
From brute force to optimal
For a guess t, DFS through cells ≤ t. The smallest t that reaches the corner is the answer.
- lo = grid[0][0], hi = n² − 1.
- DFS from (0,0) using cells ≤ mid.
- Shrink the range until lo == hi.
log(n²) = 2 log n guesses, each O(n²).
visited grid and DFS recursion (up to n² deep).
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 lo = grid[0][0], hi = n * n - 1; // answer is at least the start cell while (lo < hi) { int mid = lo + (hi - lo) / 2; if (reach(grid, 0, 0, mid, new boolean[n][n])) hi = mid; else lo = mid + 1; } return lo; } /** Can we reach the corner using only cells with elevation ≤ 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; for (int[] d : DIRS) if (reach(g, r + d[0], c + d[1], t, seen)) return true; return false; }}Always swim into the lowest reachable cell next. Track the highest elevation seen on the way; when the corner pops, that's the answer.
- Heap of {max elevation so far, r, c}, start {grid[0][0], 0, 0}.
- Poll; if it's the corner, return its value.
- Push unseen neighbours with max(t, grid[nr][nc]); mark them seen.
n² cells pushed once each; heap ops O(log n²) = O(log n).
seen grid and heap.
class Solution { private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; public int swimInWater(int[][] grid) { int n = grid.length; boolean[][] seen = new boolean[n][n]; PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])); pq.offer(new int[] {grid[0][0], 0, 0}); // {highest cell on the path, row, col} seen[0][0] = true; while (!pq.isEmpty()) { int[] top = pq.poll(); int t = top[0], r = top[1], c = top[2]; if (r == n - 1 && c == n - 1) return t; for (int[] d : DIRS) { int nr = r + d[0], nc = c + d[1]; if (nr < 0 || nc < 0 || nr >= n || nc >= n || seen[nr][nc]) continue; seen[nr][nc] = true; // first push is already the best (minimax) pq.offer(new int[] {Math.max(t, grid[nr][nc]), nr, nc}); } } return -1; }}Flip the question: flood cells in order of elevation. Because elevations are a permutation, the cell with value t floods exactly at time t. Union it with already-flooded neighbours; the first t at which the corners share a root is the answer.
- cellAt[value] = cell (no sorting needed).
- For t = 0, 1, …: union cellAt[t] with neighbours ≤ t.
- Return the first t where find(start) == find(end).
n² cells, ≤ 4 unions each, and no sort thanks to the permutation. Union by size + path compression make each find/union near O(1) — slightly better than the heap's log factor.
parent, size and cellAt arrays.
class Solution { private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; private int[] parent, size; private int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); return parent[x]; } private void union(int a, int b) { int ra = find(a), rb = find(b); if (ra == rb) return; if (size[ra] < size[rb]) { int t = ra; ra = rb; rb = t; } parent[rb] = ra; // smaller pool hangs under the bigger one size[ra] += size[rb]; } public int swimInWater(int[][] grid) { int n = grid.length; int[] cellAt = new int[n * n]; // values are a permutation of 0..n²−1: no sort needed for (int r = 0; r < n; r++) for (int c = 0; c < n; c++) cellAt[grid[r][c]] = r * n + c; parent = new int[n * n]; size = new int[n * n]; for (int i = 0; i < n * n; i++) { parent[i] = i; size[i] = 1; } for (int t = 0; t < n * n; t++) { // water rises to level t int r = cellAt[t] / n, c = cellAt[t] % n; // the cell that just went under for (int[] d : DIRS) { int nr = r + d[0], nc = c + d[1]; if (nr < 0 || nc < 0 || nr >= n || nc >= n || grid[nr][nc] > t) continue; union(r * n + c, nr * n + nc); // join flooded neighbours } if (find(0) == find(n * n - 1)) return t; // corner to corner by water } return n * n - 1; }}Flooding dry run
in the room
How to say it in the interview
talk track
- Name the pattern: "minimise the maximum along a path."
- Mention all three approaches; code the heap; note the union-find trick uses the permutation guarantee (no sort needed).
traps
- Forgetting that the start cell's own elevation counts: the answer is at least grid[0][0].
- Summing elevations instead of taking the max along the path.
what they ask next
Follow-ups
Why is marking seen on push safe here?
The heap pops in increasing max-height order, so the first push of a cell already carries the smallest possible max — later pushes can only be higher.
Path With Minimum Effort (LC 1631, above)?
Same minimax idea, but the cost is the biggest height difference between steps: run the same heap, or union-find over edges sorted by difference (Kruskal-style).
before you submit
Edge cases to test
- 1 × 1 grid → grid[0][0], which is 0.
- The start cell can be the highest on the best route ([[3,2],[0,1]] → 3): the answer is at least grid[0][0].
- The best route may wind around a peak instead of going straight.
practise next
Similar problems
- #1631Path With Minimum EffortMediumin Shortest Paths
- #1011Capacity To Ship Packages Within D DaysMediumin Binary Search