Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given an n x n integer matrix grid where each value grid[i][j] represents the elevation at that point (i, j).
It starts raining, and water gradually rises over time. At time t, the water level is t, meaning any cell with elevation less than equal to t is submerged or reachable.
You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t. You can swim infinite distances in zero time. Of course, you must stay within the boundaries of the grid during your swim.
Return the minimum time until you can reach the bottom right square (n - 1, n - 1) if you start at the top left square (0, 0).
Example 1:
Input: grid = [[0,2],[1,3]] Output: 3 Explanation: At time 0, you are in grid location (0, 0). You cannot go anywhere else because 4-directionally adjacent neighbors have a higher elevation than t = 0. You cannot reach point (1, 1) until time 3. When the depth of water is 3, we can swim anywhere inside the grid.
Example 2:
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 final route is shown. We need to wait until time 16 so that (0, 0) and (4, 4) are connected.
Constraints:
n == grid.lengthn == grid[i].length1 <= n <= 500 <= grid[i][j] < n2grid[i][j] is unique.Problem summary: You are given an n x n integer matrix grid where each value grid[i][j] represents the elevation at that point (i, j). It starts raining, and water gradually rises over time. At time t, the water level is t, meaning any cell with elevation less than equal to t is submerged or reachable. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t. You can swim infinite distances in zero time. Of course, you must stay within the boundaries of the grid during your swim. Return the minimum time until you can reach the bottom right square (n - 1, n - 1) if you start at the top left square (0, 0).
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Binary Search · Union-Find
[[0,2],[1,3]]
[[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]]
path-with-minimum-effort)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #778: Swim in Rising Water
class Solution {
public int swimInWater(int[][] grid) {
int n = grid.length;
int m = n * n;
int[] p = new int[m];
Arrays.setAll(p, i -> i);
IntUnaryOperator find = new IntUnaryOperator() {
@Override
public int applyAsInt(int x) {
if (p[x] != x) p[x] = applyAsInt(p[x]);
return p[x];
}
};
int[] hi = new int[m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
hi[grid[i][j]] = i * n + j;
}
}
int[] dirs = {-1, 0, 1, 0, -1};
for (int t = 0; t < m; t++) {
int id = hi[t];
int x = id / n, y = id % n;
for (int k = 0; k < 4; k++) {
int nx = x + dirs[k], ny = y + dirs[k + 1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] <= t) {
int a = find.applyAsInt(x * n + y);
int b = find.applyAsInt(nx * n + ny);
p[a] = b;
}
}
if (find.applyAsInt(0) == find.applyAsInt(m - 1)) {
return t;
}
}
return 0;
}
}
// Accepted solution for LeetCode #778: Swim in Rising Water
func swimInWater(grid [][]int) int {
n := len(grid)
m := n * n
p := make([]int, m)
for i := range p {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
hi := make([]int, m)
for i := range grid {
for j, h := range grid[i] {
hi[h] = i*n + j
}
}
dirs := []int{-1, 0, 1, 0, -1}
for t := 0; t < m; t++ {
id := hi[t]
x, y := id/n, id%n
for k := 0; k < 4; k++ {
nx, ny := x+dirs[k], y+dirs[k+1]
if nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] <= t {
a := find(x*n + y)
b := find(nx*n + ny)
p[a] = b
}
}
if find(0) == find(m-1) {
return t
}
}
return 0
}
# Accepted solution for LeetCode #778: Swim in Rising Water
class Solution:
def swimInWater(self, grid: List[List[int]]) -> int:
def find(x: int) -> int:
if p[x] != x:
p[x] = find(p[x])
return p[x]
n = len(grid)
m = n * n
p = list(range(m))
hi = [0] * m
for i, row in enumerate(grid):
for j, h in enumerate(row):
hi[h] = i * n + j
dirs = (-1, 0, 1, 0, -1)
for t in range(m):
x, y = divmod(hi[t], n)
for dx, dy in pairwise(dirs):
nx, ny = x + dx, y + dy
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] <= t:
p[find(x * n + y)] = find(nx * n + ny)
if find(0) == find(m - 1):
return t
return 0
// Accepted solution for LeetCode #778: Swim in Rising Water
impl Solution {
pub fn swim_in_water(grid: Vec<Vec<i32>>) -> i32 {
let n = grid.len();
let m = n * n;
let mut p: Vec<usize> = (0..m).collect();
let mut hi = vec![0usize; m];
for i in 0..n {
for j in 0..n {
hi[grid[i][j] as usize] = i * n + j;
}
}
fn find(x: usize, p: &mut Vec<usize>) -> usize {
if p[x] != x {
p[x] = find(p[x], p);
}
p[x]
}
let dirs = [-1isize, 0, 1, 0, -1];
for t in 0..m {
let id = hi[t];
let x = id / n;
let y = id % n;
for k in 0..4 {
let nx = x as isize + dirs[k];
let ny = y as isize + dirs[k + 1];
if nx >= 0 && nx < n as isize && ny >= 0 && ny < n as isize {
let nx = nx as usize;
let ny = ny as usize;
if grid[nx][ny] as usize <= t {
let a = find(x * n + y, &mut p);
let b = find(nx * n + ny, &mut p);
p[a] = b;
}
}
}
if find(0, &mut p) == find(m - 1, &mut p) {
return t as i32;
}
}
0
}
}
// Accepted solution for LeetCode #778: Swim in Rising Water
function swimInWater(grid: number[][]): number {
const n = grid.length;
const m = n * n;
const p = Array.from({ length: m }, (_, i) => i);
const hi = new Array<number>(m);
const find = (x: number): number => (p[x] === x ? x : (p[x] = find(p[x])));
for (let i = 0; i < n; ++i) {
for (let j = 0; j < n; ++j) {
hi[grid[i][j]] = i * n + j;
}
}
const dirs = [-1, 0, 1, 0, -1];
for (let t = 0; t < m; ++t) {
const id = hi[t];
const x = Math.floor(id / n);
const y = id % n;
for (let k = 0; k < 4; ++k) {
const nx = x + dirs[k];
const ny = y + dirs[k + 1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] <= t) {
p[find(x * n + y)] = find(nx * n + ny);
}
}
if (find(0) === find(m - 1)) {
return t;
}
}
return 0;
}
Use this to step through a reusable interview workflow for this problem.
Check every element from left to right until we find the target or exhaust the array. Each comparison is O(1), and we may visit all n elements, giving O(n). No extra space needed.
Each comparison eliminates half the remaining search space. After k comparisons, the space is n/2ᵏ. We stop when the space is 1, so k = log₂ n. No extra memory needed — just two pointers (lo, hi).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.
Usually fails on: Two-element ranges never converge.
Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.