Browse curriculum

Swim in Rising Water

Find the earliest time you can swim from the top-left to the bottom-right of an elevation grid with Dijkstra, where a path costs its highest cell.

Problem Understanding

Swim in Rising Water: grid[r][c] is the elevation of each cell in an n × n grid. At time t the water level is t, and you can swim between adjacent cells whose elevations are both at most t, instantly. Return the least time you can reach (n-1, n-1) starting from (0, 0).

Example: [[0,2],[1,3]] returns 3: you cannot leave the corner before t = 3, because the target itself is 3.

The Intuition: A Path Costs Its Highest Cell

Swimming is instant, so only the water level matters. A route becomes usable once the water covers its highest cell. The question is therefore: which path from corner to corner has the smallest maximum elevation?

  1. Treat it as shortest path where a path's cost is max, not +.
  2. Run Dijkstra with a min-heap of (time, cell): pop the cell reachable at the lowest time, then push its neighbours with max(time, elevation).
  3. The first time the bottom-right cell is popped, its time is the answer.

It is the same loop as Network Delay Time, on a grid instead of a graph and with max in place of +.

Interactive Walkthrough

Each cell shows its elevation; the number under it is the earliest time found to reach it. Settled cells are tinted and the best path turns green at the end. Try Spiral to see the search hug the low edge and skip the high centre.

Dry Run Table

Input: grid = [[0,2],[1,3]]

Pop (time, cell) Pushed Heap after
(0, (0,0)) (1,0) at max(0,1) = 1, (0,1) at max(0,2) = 2 (1, (1,0)), (2, (0,1))
(1, (1,0)) (1,1) at max(1,3) = 3 (2, (0,1)), (3, (1,1))
(2, (0,1)) none, (1,1) already seen (3, (1,1))
(3, (1,1)) target reached

Answer: 3.

The Solution Template

Min-heap Dijkstra keyed on the highest elevation so far. JavaScript and TypeScript include a small binary heap. C uses the O(n⁴) array scan instead of a heap, which is fine for n ≤ 50.

Swim in Rising Water Code
1var swimInWater = function(grid) {
2 const n = grid.length, seen = new Set(['0,0']);
3 const heap = [[grid[0][0], 0, 0]];
4 while (heap.length) {
5 const [t, r, c] = pop(heap);
6 if (r === n - 1 && c === n - 1) return t;
7 for (const [nr, nc] of [[r + 1, c], [r - 1, c], [r, c + 1], [r, c - 1]]) {
8 if (nr < 0 || nr >= n || nc < 0 || nc >= n || seen.has(nr + ',' + nc)) continue;
9 seen.add(nr + ',' + nc);
10 push(heap, [Math.max(t, grid[nr][nc]), nr, nc]);
11 }
12 }
13};
14
15// Minimal binary min-heap on [time, row, col].
16function push(heap, item) {
17 heap.push(item);
18 let i = heap.length - 1;
19 while (i > 0) {
20 const p = (i - 1) >> 1;
21 if (heap[p][0] <= heap[i][0]) break;
22 [heap[p], heap[i]] = [heap[i], heap[p]];
23 i = p;
24 }
25}
26
27function pop(heap) {
28 const top = heap[0], last = heap.pop();
29 if (heap.length) {
30 heap[0] = last;
31 let i = 0;
32 while (true) {
33 let m = i;
34 const l = 2 * i + 1, r = l + 1;
35 if (l < heap.length && heap[l][0] < heap[m][0]) m = l;
36 if (r < heap.length && heap[r][0] < heap[m][0]) m = r;
37 if (m === i) break;
38 [heap[m], heap[i]] = [heap[i], heap[m]];
39 i = m;
40 }
41 }
42 return top;
43}

Edge Cases & Common Mistakes

  • High start cell: you cannot move before t = grid[0][0]; seeding the heap with that value handles it.
  • High target cell: the answer is at least grid[n-1][n-1], as in [[0,2],[1,3]].
  • Adding elevations: the cost is the maximum on the path, not the sum.
  • Marking cells seen on pop instead of push: still correct, but the same cell can then sit in the heap several times.
  • BFS without a heap: fewest steps is not lowest water level; a long low route can beat a short high one.

Summary

A path is usable once the water covers its highest cell, so run Dijkstra with a min-heap keyed on max(time, elevation). The first pop of the bottom-right cell gives the answer. O(n² log n) time and O(n²) space.

More practice: DSA interview questions with answers · patterns cheat sheet

The approach, step by step

  1. Start at the top-left

    Push (grid[0][0], 0, 0) and mark it seen.

  2. Pop the lowest time

    Pop (t, r, c). If it is the bottom-right cell, return t.

  3. Push the neighbours

    For each unseen neighbour, mark it seen and push (max(t, its elevation), neighbour).

Frequently asked questions

Why is Swim in Rising Water a Dijkstra problem?

At time t you can stand on every cell with elevation at most t, so the time to reach a cell along a path is the maximum elevation on that path. Finding the path with the smallest maximum is a shortest-path problem where the cost combines with max instead of +, and Dijkstra with a min-heap still works because max never decreases along a path.


What other approaches work?

Binary search on t with a BFS or DFS that only steps onto cells <= t: O(n² log n²). Or Kruskal-style union-find: add cells in order of elevation and stop when the two corners join. All three run in about O(n² log n).


Why mark a cell as seen when it is pushed?

With a max-cost, the first time a cell is reached from a popped cell is already the best it can get, because the popped cell had the lowest time in the heap. Marking it on push keeps each cell in the heap at most once.

Applying for software engineering roles? Free, no sign-up:

AI resume checker — match your resume to the job description