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.
Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the volume of water it can trap after raining.
Example 1:
Input: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] Output: 4 Explanation: After the rain, water is trapped between the blocks. We have two small ponds 1 and 3 units trapped. The total volume of water trapped is 4.
Example 2:
Input: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]] Output: 10
Constraints:
m == heightMap.lengthn == heightMap[i].length1 <= m, n <= 2000 <= heightMap[i][j] <= 2 * 104Problem summary: Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the volume of water it can trap after raining.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array
[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
[[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
trapping-rain-water)maximum-number-of-points-from-grid-queries)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #407: Trapping Rain Water II
class Solution {
public int trapRainWater(int[][] heightMap) {
int m = heightMap.length, n = heightMap[0].length;
boolean[][] vis = new boolean[m][n];
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {
pq.offer(new int[] {heightMap[i][j], i, j});
vis[i][j] = true;
}
}
}
int ans = 0;
int[] dirs = {-1, 0, 1, 0, -1};
while (!pq.isEmpty()) {
var p = pq.poll();
for (int k = 0; k < 4; ++k) {
int x = p[1] + dirs[k], y = p[2] + dirs[k + 1];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y]) {
ans += Math.max(0, p[0] - heightMap[x][y]);
vis[x][y] = true;
pq.offer(new int[] {Math.max(p[0], heightMap[x][y]), x, y});
}
}
}
return ans;
}
}
// Accepted solution for LeetCode #407: Trapping Rain Water II
func trapRainWater(heightMap [][]int) (ans int) {
m, n := len(heightMap), len(heightMap[0])
pq := hp{}
vis := make([][]bool, m)
for i, row := range heightMap {
vis[i] = make([]bool, n)
for j, v := range row {
if i == 0 || i == m-1 || j == 0 || j == n-1 {
heap.Push(&pq, tuple{v, i, j})
vis[i][j] = true
}
}
}
dirs := []int{-1, 0, 1, 0, -1}
for len(pq) > 0 {
p := heap.Pop(&pq).(tuple)
for k := 0; k < 4; k++ {
x, y := p.i+dirs[k], p.j+dirs[k+1]
if x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] {
ans += max(0, p.v-heightMap[x][y])
vis[x][y] = true
heap.Push(&pq, tuple{max(p.v, heightMap[x][y]), x, y})
}
}
}
return
}
type tuple struct{ v, i, j int }
type hp []tuple
func (h hp) Len() int { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].v < h[j].v }
func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(v any) { *h = append(*h, v.(tuple)) }
func (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }
# Accepted solution for LeetCode #407: Trapping Rain Water II
class Solution:
def trapRainWater(self, heightMap: List[List[int]]) -> int:
m, n = len(heightMap), len(heightMap[0])
vis = [[False] * n for _ in range(m)]
pq = []
for i in range(m):
for j in range(n):
if i == 0 or i == m - 1 or j == 0 or j == n - 1:
heappush(pq, (heightMap[i][j], i, j))
vis[i][j] = True
ans = 0
dirs = (-1, 0, 1, 0, -1)
while pq:
h, i, j = heappop(pq)
for a, b in pairwise(dirs):
x, y = i + a, j + b
if x >= 0 and x < m and y >= 0 and y < n and not vis[x][y]:
ans += max(0, h - heightMap[x][y])
vis[x][y] = True
heappush(pq, (max(h, heightMap[x][y]), x, y))
return ans
// Accepted solution for LeetCode #407: Trapping Rain Water II
struct Solution;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
impl Solution {
fn trap_rain_water(height_map: Vec<Vec<i32>>) -> i32 {
let n = height_map.len();
let m = height_map[0].len();
let mut queue: BinaryHeap<(Reverse<i32>, usize, usize)> = BinaryHeap::new();
let mut visited = vec![vec![false; m]; n];
for i in 0..n {
for j in 0..m {
if i == 0 || j == 0 || i == n - 1 || j == m - 1 {
visited[i][j] = true;
queue.push((Reverse(height_map[i][j]), i, j));
}
}
}
let mut res = 0;
while let Some((Reverse(h), i, j)) = queue.pop() {
if i > 0 && !visited[i - 1][j] {
let ii = i - 1;
visited[ii][j] = true;
if h > height_map[ii][j] {
res += h - height_map[ii][j];
queue.push((Reverse(h), ii, j));
} else {
queue.push((Reverse(height_map[ii][j]), ii, j));
}
}
if j > 0 && !visited[i][j - 1] {
let jj = j - 1;
visited[i][jj] = true;
if h > height_map[i][jj] {
res += h - height_map[i][jj];
queue.push((Reverse(h), i, jj));
} else {
queue.push((Reverse(height_map[i][jj]), i, jj));
}
}
if i + 1 < n && !visited[i + 1][j] {
let ii = i + 1;
visited[ii][j] = true;
if h > height_map[ii][j] {
res += h - height_map[ii][j];
queue.push((Reverse(h), ii, j));
} else {
queue.push((Reverse(height_map[ii][j]), ii, j));
}
}
if j + 1 < m && !visited[i][j + 1] {
let jj = j + 1;
visited[i][jj] = true;
if h > height_map[i][jj] {
res += h - height_map[i][jj];
queue.push((Reverse(h), i, jj));
} else {
queue.push((Reverse(height_map[i][jj]), i, jj));
}
}
}
res
}
}
#[test]
fn test() {
let height_map = vec_vec_i32![[1, 4, 3, 1, 3, 2], [3, 2, 1, 3, 2, 4], [2, 3, 3, 2, 3, 1]];
let res = 4;
assert_eq!(Solution::trap_rain_water(height_map), res);
}
// Accepted solution for LeetCode #407: Trapping Rain Water II
function trapRainWater(heightMap: number[][]): number {
const m = heightMap.length;
const n = heightMap[0].length;
const vis: boolean[][] = Array.from({ length: m }, () => new Array(n).fill(false));
const pq = new PriorityQueue<number[]>((a, b) => a[0] - b[0]);
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
pq.enqueue([heightMap[i][j], i, j]);
vis[i][j] = true;
}
}
}
let ans = 0;
const dirs = [-1, 0, 1, 0, -1];
while (!pq.isEmpty()) {
const [h, i, j] = pq.dequeue();
for (let k = 0; k < 4; ++k) {
const x = i + dirs[k];
const y = j + dirs[k + 1];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y]) {
ans += Math.max(0, h - heightMap[x][y]);
vis[x][y] = true;
pq.enqueue([Math.max(h, heightMap[x][y]), x, y]);
}
}
}
return ans;
}
Use this to step through a reusable interview workflow for this problem.
Two nested loops check every pair or subarray. The outer loop fixes a starting point, the inner loop extends or searches. For n elements this gives up to n²/2 operations. No extra space, but the quadratic time is prohibitive for large inputs.
Most array problems have an O(n²) brute force (nested loops) and an O(n) optimal (single pass with clever state tracking). The key is identifying what information to maintain as you scan: a running max, a prefix sum, a hash map of seen values, or two pointers.
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.