Skip to content
Reliable Data Engineering
Practice problem hard monotonic-stacktwo-pointersarrays
Practise with timer, notes and rubric

Trapping Rain Water

Pattern: Monotonic Stack · Difficulty: Hard · Asked at: Amazon, Google, Goldman Sachs, Meta

Classic version: LeetCode 42

Problem

heights[i] is the height of a bar of width 1. After rain, water collects between bars. Return the total units of water trapped.

Examples

trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]) → 6
trap([4, 2, 0, 3, 2, 5])                   → 9

Constraints

Starter code

def trap(heights: list[int]) -> int:
    pass

Hints

Hint 1

Water above bar i = min(highest bar to its left, highest bar to its right) - heights[i] (if positive).

Hint 2

Two pointers: whichever side has the lower running max is the limiting side. Its water is known now, so move that pointer.

Where this shows up in data engineering

Mostly a reasoning test: can you derive the per-position formula, then remove the O(n) precomputed arrays with an argument about which side is binding? That same move, from precomputing to a streaming invariant, is what turns a two-pass batch job into a one-pass stream.

Solution

def trap(heights: list[int]) -> int:
    lo, hi = 0, len(heights) - 1
    left_max = right_max = water = 0
    while lo < hi:
        if heights[lo] < heights[hi]:
            # the right side has a bar at least this tall, so left_max is the binding wall
            left_max = max(left_max, heights[lo])
            water += left_max - heights[lo]
            lo += 1
        else:
            right_max = max(right_max, heights[hi])
            water += right_max - heights[hi]
            hi -= 1
    return water

Tests

Your solution should pass these:

assert trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]) == 6
assert trap([4, 2, 0, 3, 2, 5]) == 9
assert trap([]) == 0
assert trap([3]) == 0
assert trap([1, 2, 3, 4]) == 0
assert trap([5, 0, 5]) == 5
assert trap([2, 0, 2, 0, 2]) == 4

Explanation

Formula: water[i] = max(0, min(maxL[i], maxR[i]) - h[i]). Precomputing maxL and maxR arrays gives an easy O(n) time, O(n) space solution: a great first answer.

Two pointers, O(1) space: we always advance the side with the lower bar. Suppose h[lo] < h[hi]. Every bar we’ve moved past on the left came from a step where the left was the lower side, so each was shorter than some bar at or right of the current hi; together with h[lo] < h[hi] that gives left_max ≤ (tallest bar at or right of hi) ≤ maxR[lo]. Hence min(maxL[lo], maxR[lo]) = left_max (after including h[lo]), so the water above lo is already known and lo can advance. The right side is symmetric.

Monotonic stack alternative: keep a decreasing stack; when a taller bar arrives, pop the “bottom” and add the water bounded by the new bar and the bar below on the stack (layer by layer, horizontally). Also O(n).

Complexity: O(n) time, O(1) space.

Follow-up questions

Explain the stack version in one sentence.

Each pop of a bar mid with a left wall stack[-1] and right wall i adds (min(h[left], h[i]) - h[mid]) * (i - left - 1), filling water in horizontal layers.

2D version (a height map grid)?

Trapping Rain Water II: a min-heap seeded with the border cells, expanded inward (a Dijkstra-like flood fill): water at a cell = max(0, current boundary height − cell height). O(RC log(RC)).