150NeetCode 150

96.Swim in Rising Water

HardGraphs · 17/19

Problem

You are given an n x n grid where grid[i][j] is the elevation at cell (i, j). Rain starts falling, and at time t the water level everywhere is t. You can swim from a cell to a 4-directionally adjacent cell if both cells have elevation at most t; swimming itself takes no time.

Starting at (0, 0), return the least time t at which you can reach the bottom-right cell (n - 1, n - 1).

Examples

Input:  grid = [[0,2],
                [1,3]]
Output: 3
Explanation: (1,1) has elevation 3, so you must wait until t = 3 no matter which path you take.
Input:  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]]
Output: 16
Explanation: The best path snakes 0-1-2-3-4-5-16-15-14-13-12-11-10-9-8-7-6; its highest cell is 16.

Constraints

  • n == grid.length == grid[i].length
  • 1 <= n <= 50
  • 0 <= grid[i][j] < n²
  • Every value grid[i][j] is unique.

💡 Key Insight

The time needed for a path equals the maximum elevation on that path, so we want the path that minimises its maximum cell (a minimax / "bottleneck" path). Dijkstra works with a modified cost: instead of summing weights, the cost of reaching a neighbour is max(costSoFar, elevation). Because this cost never decreases along a path, the first time the heap pops (n-1, n-1) its cost is optimal.

Brute Force — Try Every Time

TimeO(n⁴)SpaceO(n²)

Intuition

For a fixed t, the question "can I reach the corner?" is a simple flood fill over cells with elevation <= t. Try t = grid[0][0], grid[0][0] + 1, ... and return the first t for which the corner is reachable.

Algorithm

  1. For t from grid[0][0] up to n² - 1:
  2. Run DFS from (0, 0) visiting only cells with grid <= t.
  3. If DFS reaches (n - 1, n - 1), return t.

Visualization

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

t=0: reachable {(0,0)}                 corner? no
t=1: reachable {(0,0),(1,0)}           no
t=2: reachable {(0,0),(1,0),(0,1)}     no
t=3: corner (1,1) now open             yes -> 3

Code

java
class Solution {
    private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    public int swimInWater(int[][] grid) {
        int n = grid.length;
        for (int t = grid[0][0]; ; t++) {
            if (canReach(grid, 0, 0, t, new boolean[n][n])) {
                return t;
            }
        }
    }

    private boolean canReach(int[][] grid, int r, int c, int t, boolean[][] seen) {
        int n = grid.length;
        if (r == n - 1 && c == n - 1) {
            return true;
        }
        seen[r][c] = true;
        for (int[] d : DIRS) {
            int nr = r + d[0], nc = c + d[1];
            if (nr >= 0 && nr < n && nc >= 0 && nc < n && !seen[nr][nc] && grid[nr][nc] <= t) {
                if (canReach(grid, nr, nc, t, seen)) {
                    return true;
                }
            }
        }
        return false;
    }
}

Complexity

  • Time: O(n⁴) — up to n² values of t, each with an O(n²) flood fill.
  • Space: O(n²) — visited grid and recursion stack.

Complexity Comparison

Approach Time Space
Brute Force — Try Every Time O(n⁴) O(n²)
Binary Search + DFS O(n² log n) O(n²)
Dijkstra with Max-Cost O(n² log n) O(n²)

Common Pitfalls

  • Summing elevations along the path — the cost is the maximum elevation, not the total.
  • Starting the search at t = 0 and forgetting that grid[0][0] itself may be high; the answer is at least grid[0][0] (and at least grid[n-1][n-1]).
  • Marking cells visited on pop vs. push: marking on push is safe here only because a cell's cost max(time, elev) from the earliest-popped parent is already minimal; be consistent.
  • Recursion depth: a DFS over 2500 cells is fine, but much larger grids would need an iterative flood fill.