LeetCode #42 — Hard

Trapping Rain Water

A complete visual walkthrough of the two-pointer approach — watch water fill between bars in real time.

Patterns Used

Roadmap

  1. The Problem — Visualized
  2. Brute Force — Per-Bar Calculation
  3. The Core Insight — Two Pointers
  4. Two-Pointer Walkthrough
  5. Edge Cases
  6. Full Annotated Code
  7. Interactive Demo
  8. Complexity Analysis
Step 01

The Problem — Visualized

Given an elevation map as an array of non-negative integers, compute how much rainwater is trapped between the bars after raining.

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

The gray bars are the elevation. The blue areas are trapped water. Total water = 6 units.

Step 02

Brute Force — Per-Bar Calculation

For each bar at index i, the water it can hold equals:

water[i] = min(maxLeft, maxRight) - height[i]

Where maxLeft is the tallest bar to the left of i (inclusive) and maxRight is the tallest to the right (inclusive). Water level at i is limited by the shorter of the two walls.

Brute Force: O(n^2)

For each bar, scan left and right to find the maximums. This requires two nested loops.

for (int i = 0; i < n; i++) {
    int maxLeft = 0, maxRight = 0;
    for (int j = 0; j <= i; j++) maxLeft = Math.max(maxLeft, height[j]);
    for (int j = i; j < n; j++) maxRight = Math.max(maxRight, height[j]);
    water += Math.min(maxLeft, maxRight) - height[i];
}

This works but is O(n^2). We can do much better.

💡

The key question: can we avoid re-scanning for maxLeft and maxRight at every position? Yes — by tracking running maximums from each end.

Step 03

The Core Insight — Two Pointers

Place a pointer at each end of the array. Track the running maximum from the left (leftMax) and from the right (rightMax). At each step, process the side with the smaller maximum.

Why Move the Smaller Side?

If leftMax < rightMax, then we know the water at the left pointer is bounded by leftMax — regardless of what lies between the pointers. There might be even taller bars to the right, but leftMax is the bottleneck. So we can safely compute water at left and advance it.

height[left] < height[right]
Update leftMax. Water at left = leftMax - height[left]. Move left forward.
height[left] >= height[right]
Update rightMax. Water at right = rightMax - height[right]. Move right backward.

One pass, no extra arrays. Unlike the prefix-max approach (which precomputes leftMax[] and rightMax[] arrays in O(n) space), the two-pointer approach computes everything on the fly in O(1) space.

Step 04

Two-Pointer Walkthrough

Let's trace through height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] with the two-pointer approach.

Key Steps

L=0, R=11 | leftMax=0, rightMax=1
h[0]=0 < h[11]=1. leftMax=max(0,0)=0. water+=0-0=0. L→1
L=1, R=11 | leftMax=1, rightMax=1
h[1]=1 >= h[11]=1. rightMax=max(1,1)=1. water+=1-1=0. R→10
L=1, R=10 | leftMax=1, rightMax=2
h[1]=1 < h[10]=2. leftMax=max(1,1)=1. water+=1-1=0. L→2
L=2, R=10 | leftMax=1, rightMax=2
h[2]=0 < h[10]=2. leftMax=1. water+=1-0=1. L→3
... continuing ...
The pointers converge, accumulating water at each step. Use the interactive demo below to see every step.

Total water = 6

Step 05

Edge Cases

Flat surface: [2, 2, 2, 2]
No valleys. Water = 0 everywhere (leftMax - height[i] = 0).
Single bar: [5]
Cannot trap any water with one bar. Answer = 0.
Strictly increasing: [1, 2, 3, 4]
Water always runs off to the left. No right wall to trap it. Answer = 0.
Strictly decreasing: [4, 3, 2, 1]
Water always runs off to the right. Same reason, answer = 0.
V-shape: [3, 0, 3]
Water at index 1: min(3,3) - 0 = 3. Answer = 3.
Step 06

Full Annotated Code

class Solution {
    public int trap(int[] height) {
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0, water = 0;

        while (left < right) {

            if (height[left] < height[right]) {
                // Left side is the bottleneck
                leftMax = Math.max(leftMax, height[left]);
                water += leftMax - height[left];  // water at left
                left++;

            } else {
                // Right side is the bottleneck
                rightMax = Math.max(rightMax, height[right]);
                water += rightMax - height[right]; // water at right
                right--;
            }
        }

        return water;
    }
}
Step 07

Interactive Demo

Enter heights and watch the two pointers converge. The bar chart updates in real time with water filling between bars.

⚙ Two-Pointer Visualizer


Press "Step →" or "Run All" to begin.
Step 08

Complexity Analysis

Time
O(n)
Space
O(1)

Each pointer moves at most n times total, and each step does O(1) work (a comparison, a max update, and an addition). We use only four variables regardless of input size.

📝

Three approaches compared: Brute force is O(n^2) time, O(1) space. Prefix arrays are O(n) time, O(n) space. Two pointers achieves the best of both: O(n) time, O(1) space. The two-pointer approach is also simpler to code than the stack-based approach, which has the same complexity but more complex logic.