LeetCode · Problem 42

LeetCode 42 Trapping Rain Water: Four Approaches, Fully Dry-Run

One insight, water above a bar is min(tallest left, tallest right) minus its height, taken from O(n²) to O(n) with prefix arrays, a monotonic stack and finally two pointers in O(1) space, with the proof interviewers ask for and the 2D follow-up.

✍️ Pranjal Jain, Ex-Microsoft · IIT Kanpur 📅 10 October 2026 ⏱ 15 min read
Stone pillars of varying heights with rainwater trapped between them, illustrating the Trapping Rain Water problem
TL;DR
  • Insight: water above bar i = min(maxLeft[i], maxRight[i]) - height[i].
  • Brute force: compute both maxima per index by scanning. O(n²) time, O(1) space.
  • Prefix/suffix max arrays: precompute both maxima. O(n) time, O(n) space. The easiest correct answer.
  • Monotonic stack: fill water in horizontal layers when a taller bar closes a basin. O(n) time, O(n) space.
  • Two pointers: always process the shorter side; its level is fixed by its own running max. O(n) time, O(1) space. The answer interviewers want, with its justification.

Trapping Rain Water is LeetCode's most famous Hard problem and one of the most frequently reported hard questions in interviews at Amazon, Google, Microsoft, Goldman Sachs, Flipkart and many others. It is a perfect interview problem because it has a clear ladder: a brute force anyone can write, an O(n) solution once you see one formula, and an O(1)-space version that requires a genuine argument. This walkthrough climbs that ladder with full dry runs, and every implementation has been checked against the brute force on thousands of random inputs.

The Trapping Rain Water Problem

Given n non-negative integers representing an elevation map where each bar has width 1, compute how much water it can trap after raining.

Example: height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] traps 6 units. Another: [4, 2, 0, 3, 2, 5] traps 9.

# # ~ ~ ~ # # ~ # # ~ # # ~ # # # # # # ─────────────────────────────────── h: 0 1 0 2 1 0 1 3 2 1 2 1 w: 0 0 1 0 1 2 1 0 0 1 0 0 total = 6 # = bar, ~ = trapped water

Clarify before solving: heights are non-negative integers; the edges of the array spill water (nothing beyond index 0 or n-1 holds it); n can be up to 2 × 104 on LeetCode, and empty input returns 0.

The Core Insight: Water Depends on Two Maxima

Think about one column at a time. Water above index i rises until it would spill over the lower of the two "walls" around it: the tallest bar somewhere to its left, and the tallest bar somewhere to its right. So:

💡
The formulawater[i] = min(maxLeft[i], maxRight[i]) - height[i], where both maxima include index i itself. Including i guarantees the result is at least 0 (a bar taller than both sides just holds nothing). Every approach below is a different way to get those two maxima cheaply.

Applied to the example, per index:

i01234567891011
height010210132121
maxLeft011222233333
maxRight333333332221
water001012100100

Sum of the last row: 1 + 1 + 2 + 1 + 1 = 6.

Approach 1: Brute Force, O(n²)

For every index, scan left for the maximum and right for the maximum.

Python
def trap_brute(height):
    n = len(height)
    water = 0
    for i in range(n):
        left_max = max(height[:i + 1])
        right_max = max(height[i:])
        water += min(left_max, right_max) - height[i]
    return water

O(n²) time (each index scans up to n bars), O(1) extra space conceptually (the Python slices copy, so write explicit loops if the interviewer cares). At n = 2 × 104 that is about 4 × 108 steps, too slow. The waste is obvious: the left maximum for i+1 is just max(leftMax(i), height[i+1]), yet we recompute it from scratch.

Approach 2: Prefix and Suffix Maximum Arrays, O(n)

Compute maxLeft in one left-to-right pass and maxRight in one right-to-left pass, then apply the formula. This is the same "precompute a running aggregate" idea as prefix sums, with max instead of plus.

Java
public static int trapPrefix(int[] height) {
    int n = height.length;
    if (n == 0) return 0;
    int[] left = new int[n], right = new int[n];
    left[0] = height[0];
    for (int i = 1; i < n; i++) left[i] = Math.max(left[i - 1], height[i]);
    right[n - 1] = height[n - 1];
    for (int i = n - 2; i >= 0; i--) right[i] = Math.max(right[i + 1], height[i]);
    int water = 0;
    for (int i = 0; i < n; i++) water += Math.min(left[i], right[i]) - height[i];
    return water;
}
Python
def trap_prefix(height):
    n = len(height)
    if n == 0:
        return 0
    left = [0] * n
    right = [0] * n
    left[0] = height[0]
    for i in range(1, n):
        left[i] = max(left[i - 1], height[i])
    right[n - 1] = height[n - 1]
    for i in range(n - 2, -1, -1):
        right[i] = max(right[i + 1], height[i])
    return sum(min(left[i], right[i]) - height[i] for i in range(n))

O(n) time, O(n) space. The table in the previous section is exactly this algorithm's state. If you are nervous in an interview, write this one first: it is hard to get wrong, it is already optimal in time, and it sets up the two-pointer optimisation naturally ("can we avoid storing both arrays?").

Approach 3: Monotonic Stack, Filling Water Layer by Layer

A completely different view: instead of computing water per column, compute it per horizontal layer of each basin. Keep a stack of indices whose heights are non-increasing. When a bar taller than the stack top arrives, the top is the bottom of a basin, the next element down is its left wall, and the current bar is its right wall. The water in that layer is width × (min(leftWall, rightWall) - bottom).

C++
int trapStack(const vector<int>& h) {
    stack<int> st;                       // indices, heights non-increasing
    int water = 0;
    for (int i = 0; i < (int)h.size(); i++) {
        while (!st.empty() && h[st.top()] < h[i]) {
            int bottom = st.top();
            st.pop();
            if (st.empty()) break;       // no left wall: water spills
            int left = st.top();
            int width = i - left - 1;
            int bounded = min(h[left], h[i]) - h[bottom];
            water += width * bounded;
        }
        st.push(i);
    }
    return water;
}
Python
def trap_stack(height):
    stack = []          # indices, heights non-increasing from bottom to top
    water = 0
    for i, h in enumerate(height):
        while stack and height[stack[-1]] < h:
            bottom = stack.pop()
            if not stack:
                break
            left = stack[-1]
            width = i - left - 1
            bounded = min(height[left], h) - height[bottom]
            water += width * bounded
        stack.append(i)
    return water

Stack dry run on the example

i (h)Popped bottomLeft wallWidthBounded heightAddedTotal
3 (2)2 (h=0)1 (h=1)1min(1,2) - 0 = 111
6 (1)5 (h=0)4 (h=1)1min(1,1) - 0 = 112
7 (3)6 (h=1)4 (h=1)2min(1,3) - 1 = 002
7 (3)4 (h=1)3 (h=2)3min(2,3) - 1 = 135
10 (2)9 (h=1)8 (h=2)1min(2,2) - 1 = 116

Look at i = 7: the basin between bars 3 and 7 is filled in two layers. The first pop (index 6, height 1, sitting on index 4 with height 1) adds nothing because the layer is zero-height. The second pop (index 4) fills the layer from height 1 up to 2 across three columns (4, 5, 6). The bottom layer at index 5 was already filled when i = 6 arrived. Layers, not columns.

Each index is pushed once and popped at most once, so the while loop is amortised O(1): O(n) time, O(n) space in the worst case (a strictly decreasing array). The equality case does not matter: with <, equal bars stay on the stack and form zero-height layers. This stack pattern is the same one behind Largest Rectangle in Histogram; see the monotonic stack guide.

Approach 4: Two Pointers in O(1) Space

The prefix approach needs both maxima for every index. The two-pointer approach notices that you only need the smaller of them, and you can know which one is smaller without computing the other exactly.

Keep lo and hi at the two ends, with running leftMax (max of height[0..lo]) and rightMax (max of height[hi..n-1]). At each step, process the side with the shorter bar:

Java
public static int trap(int[] height) {
    int lo = 0, hi = height.length - 1;
    int leftMax = 0, rightMax = 0, water = 0;
    while (lo < hi) {
        if (height[lo] < height[hi]) {
            leftMax = Math.max(leftMax, height[lo]);
            water += leftMax - height[lo];     // left side is the binding wall
            lo++;
        } else {
            rightMax = Math.max(rightMax, height[hi]);
            water += rightMax - height[hi];    // right side is the binding wall
            hi--;
        }
    }
    return water;
}
C++
int trap(const vector<int>& h) {
    int lo = 0, hi = (int)h.size() - 1;
    int leftMax = 0, rightMax = 0, water = 0;
    while (lo < hi) {
        if (h[lo] < h[hi]) {
            leftMax = max(leftMax, h[lo]);
            water += leftMax - h[lo];
            lo++;
        } else {
            rightMax = max(rightMax, h[hi]);
            water += rightMax - h[hi];
            hi--;
        }
    }
    return water;
}
Python
def trap(height):
    lo, hi = 0, len(height) - 1
    left_max = right_max = 0
    water = 0
    while lo < hi:
        if height[lo] < height[hi]:
            left_max = max(left_max, height[lo])
            water += left_max - height[lo]
            lo += 1
        else:
            right_max = max(right_max, height[hi])
            water += right_max - height[hi]
            hi -= 1
    return water

Two-pointer dry run

StepSideIndexh[lo] vs h[hi]leftMaxrightMaxAddedTotal
1L00 < 10000
2R111 vs 1 (not <)0100
3L11 < 21100
4L20 < 21111
5R102 vs 21201
6R92 vs 11212
7R82 vs 21202
8L32 < 32202
9L41 < 32213
10L50 < 32225
11L61 < 32216

The pointers meet at index 7 (the tallest bar, which never holds water). Total 6. Notice the per-index additions match the "water" row of the formula table exactly; two pointers just computes the same numbers in a different order.

Why the Two-Pointer Approach Is Correct

This is the part interviewers actually test. Suppose height[lo] < height[hi] and we are about to process lo.

  1. leftMax is exact for lo. It is the maximum of height[0..lo], which we have fully scanned.
  2. The true maxRight[lo] is at least height[hi], since hi is to the right of lo.
  3. leftMax ≤ maxRight[lo]. Invariant: every time lo advanced, it was because height[lo] < height[hi] for the hi at that moment, and hi only moves past bars that are not taller than some bar on the left side (the else-branch runs when height[lo] >= height[hi]). So the largest bar seen on the left so far never exceeds the bar currently at hi, or one to its right. Hence leftMax ≤ max(height[hi..n-1]) ≤ maxRight[lo].
  4. So min(maxLeft[lo], maxRight[lo]) = leftMax, and the water at lo is exactly leftMax - height[lo]. The symmetric argument handles the right side.

An easy way to say it in an interview: "I process the shorter side because a taller bar is guaranteed on the other side, so this side's water is limited only by its own running max, which I know exactly."

⚠️
Comparing the wrong thingsA common bug is branching on leftMax < rightMax but forgetting to update the max before adding water, which can make leftMax - height[lo] negative. Branching on height[lo] < height[hi] and updating the max first, as above, keeps every addition non-negative. Branching on the maxima also works if you update first; pick one form and stick to it.

Comparing the Four Approaches

ApproachTimeExtra spaceHow it thinksInterview role
Brute forceO(n²)O(1)Per column, scan both sidesState it, do not code it
Prefix/suffix maxO(n)O(n)Per column, precomputed wallsSafe first implementation
Monotonic stackO(n)O(n)Per basin layer, horizontallyShows pattern breadth; useful for streaming input
Two pointersO(n)O(1)Per column, binding side onlyThe expected optimal; needs the proof

A nuance worth mentioning: the stack approach processes bars strictly left to right, so it works when heights arrive as a stream and you cannot see the right end. Two pointers needs random access to both ends. That is a good answer to "which would you use if the data came from a stream?"

Trapping Rain Water II: The 2D Version (LeetCode 407)

Now the elevation map is an m × n grid and water can escape in four directions. The 1D idea "the shorter wall bounds the water" generalises: water in a cell is bounded by the lowest point on the boundary of the region enclosing it. Process cells from the outside in with a min-heap, always expanding from the lowest boundary cell, like Dijkstra:

Python
import heapq

def trap_rain_water_2d(height_map):
    if not height_map or not height_map[0]:
        return 0
    m, n = len(height_map), len(height_map[0])
    visited = [[False] * n for _ in range(m)]
    heap = []
    for r in range(m):
        for c in range(n):
            if r in (0, m - 1) or c in (0, n - 1):      # outer boundary
                heapq.heappush(heap, (height_map[r][c], r, c))
                visited[r][c] = True
    water = 0
    while heap:
        h, r, c = heapq.heappop(heap)                    # lowest wall so far
        for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            nr, nc = r + dr, c + dc
            if 0 <= nr < m and 0 <= nc < n and not visited[nr][nc]:
                visited[nr][nc] = True
                water += max(0, h - height_map[nr][nc])
                heapq.heappush(heap, (max(h, height_map[nr][nc]), nr, nc))
    return water

On LeetCode's examples this returns 4 and 10. Time O(mn log(mn)), space O(mn). Why it works: when the lowest boundary level is h, any unvisited neighbour can hold water up to h, because every other escape path passes through a boundary at least as high. The pushed level max(h, height) represents the new effective wall. Note that the 1D two-pointer trick does not extend to 2D, and candidates who try "min of max in four directions" get wrong answers, since water can flow around obstacles.

  • Container With Most Water (LC 11): choose two lines, maximise the rectangle between them; inner bars are ignored. Also two pointers moving the shorter side, but you maximise one area instead of summing columns.
  • Largest Rectangle in Histogram (LC 84): the same monotonic stack, popping when a shorter bar arrives, to find each bar's maximal width.
  • Pour Water (LC 755): simulation of droplets; uses the same "lowest reachable point" thinking.
  • Product of Array Except Self (LC 238): the same prefix-from-left, suffix-from-right precomputation with a product; see the walkthrough.
  • Best Time to Buy and Sell Stock (LC 121): the running-maximum/minimum idea in its simplest form.

How Trapping Rain Water Is Asked in Interviews

A strong performance usually follows this script:

  1. Clarify: non-negative heights, width 1, edges spill, empty input returns 0.
  2. Derive the formula from one column, with a tiny example, before any code.
  3. Mention brute force and its O(n²) cost, then name the waste (recomputing maxima).
  4. Code prefix/suffix arrays or go straight to two pointers if confident.
  5. Optimise space to O(1) and give the "shorter side is bounded by its own max" argument.
  6. Dry run on [4, 2, 0, 3, 2, 5] (answer 9) or the standard example, plus edge cases: fewer than 3 bars (0), strictly increasing or decreasing (0), all equal (0).

Follow-ups to expect: "do it in one pass" (two pointers already is), "what if heights stream in" (stack), "2D version" (heap), "what if bars can have different widths" (weight each column's water by its width; the formula is unchanged), and "return the water per index" (store additions in an array). Red flags: memorised two-pointer code with no explanation, or confusing it with Container With Most Water. More two-pointer reasoning practice is in the two pointers guide.

✅
Remember it in one lineWater at a bar is the lower of the two tallest walls minus the bar; process the shorter side because the other side is guaranteed to be at least as tall.

Frequently Asked Questions

What is the formula for Trapping Rain Water?▾
The water above index i is min(maxLeft[i], maxRight[i]) - height[i], where maxLeft[i] is the tallest bar from the start up to i and maxRight[i] is the tallest bar from i to the end, both including i. Summing that over all indices gives the total. Including the bar itself in both maxima guarantees the value is never negative.
What is the optimal solution for Trapping Rain Water?▾
The two-pointer approach runs in O(n) time and O(1) extra space. Pointers start at both ends; whichever side has the shorter bar is processed, because its water level is fully determined by the running maximum on its own side, since a taller bar is known to exist on the other side.
Why does the two-pointer solution move the smaller side?▾
If height[lo] < height[hi], then the true right maximum for lo is at least height[hi], which is greater than height[lo]. The water at lo is limited by min(leftMax, rightMax), and as long as leftMax is not larger than some bar on the right, leftMax is the binding constraint. So the water at lo equals leftMax - height[lo] and can be finalised without knowing the exact right maximum.
How does the stack solution for Trapping Rain Water work?▾
It keeps indices of bars in non-increasing height order. When a taller bar arrives, it pops the top as the bottom of a basin; the new top of the stack is the left wall and the current bar is the right wall. It adds width times (min of the two walls minus the bottom height), filling water in horizontal layers rather than column by column. Each index is pushed and popped once, so it is O(n).
What is the difference between Trapping Rain Water and Container With Most Water?▾
Container With Most Water picks two lines and measures the area between them, ignoring every line in between, so it returns a single rectangle. Trapping Rain Water sums water above every bar, and the bars in between matter because they displace water. Both use two pointers that move the shorter side, but the reasoning and the accumulated quantity differ.
How do you solve Trapping Rain Water II in 2D?▾
Use a min-heap seeded with all boundary cells, like Dijkstra from the outside in. Repeatedly pop the lowest boundary cell; for each unvisited neighbour, add max(0, current level - neighbour height) to the answer and push the neighbour with level max(current level, its height). This runs in O(mn log(mn)) time.
Is Trapping Rain Water a hard problem in interviews?▾
It is labelled Hard on LeetCode and is one of the most frequently reported hard problems at Amazon, Google, Microsoft, Goldman Sachs and many Indian product companies. Once you see the min(left max, right max) insight it becomes approachable, and interviewers mostly judge whether you can derive the O(1)-space version and justify it.

Crack the Hard Problems Interviewers Love

Prepflix's DSA program walks through classics like Trapping Rain Water from brute force to optimal, so you learn to derive the insight live instead of memorising code.

Explore the Prepflix Course →
Pranjal Jain
Pranjal Jain

Ex-Microsoft SDE · IIT Kanpur · Founder of Prepflix. Helps engineers crack startup and product company interviews across India.