Trapping Rain Water
Water above a bar is the smaller of the tallest walls on each side, minus its own height.
Approach
Two pointers track the max height seen from the left and right. Whichever side has the smaller running max is the bottleneck, so you can safely add its trapped water and advance it. This computes the answer in one pass and O(1) space; a monotonic stack is an alternative that fills water layer by layer.
Time complexity
O(n)
Space complexity
O(1)
Common mistake
Recomputing the max-left/max-right for every index (O(n²)) instead of maintaining them incrementally.
Interactive walkthrough — step through it below, free and without an account
Problem
Given an array of nonnegative bar heights, determine how many units of water remain after rain falls between the bars. Water can sit above a bar only when taller boundaries exist on both sides, and the amount is limited by the shorter boundary.
Examples
Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Input: height = [4,2,0,3,2,5]
Output: 9
Problem
Problem
Given an array of nonnegative bar heights, determine how many units of water remain after rain falls between the bars. Water can sit above a bar only when taller boundaries exist on both sides, and the amount is limited by the shorter boundary.
Problem and Goal
Learning goal: Recognize what determines the water level above each bar.
Core idea
For an index i, water is bounded by the tallest wall anywhere to its left and the tallest wall anywhere to its right. The water level is the smaller of those two wall heights. After subtracting height[i], any positive remainder is trapped water.
Definition
For a bar at i, water[i] = max(0, min(maxLeft[i], maxRight[i]) - height[i]).
Algorithm plan
- Identify the left and right boundaries of the array.
- Reason about the best wall seen from each side.
- Add only positive contributions above the current bar.
Micro-example
For [3,1,2], the middle bar sees left maximum 3 and right maximum 2.
Its water level is min(3,2) = 2, so it traps 2 - 1 = 1 unit.
Pitfalls
- A bar at an edge cannot trap water because one side has no wall.
- The taller wall does not determine the current amount by itself; the shorter side is the bottleneck.
- Never add a negative amount when a bar reaches or exceeds its boundary.
Recap
- Water depends on the smaller tallest wall on the two sides.
- Each bar contributes independently once those boundaries are known.
- The two-pointer method maintains these boundaries without building arrays.
Problem and Goal
Water is capped by the shorter wall
At each bar, the water level is `min(maxLeft, maxRight)`. Subtract the bar height and keep only positive volume.
The smaller of the two running maxima is the safe side to process.
Key Idea: Two Running Maxima
Learning goal: Understand why one side can be processed safely before the other.
Core idea
Keep pointers left and right, plus leftMax and rightMax, the tallest bars encountered so far from each side. If leftMax <= rightMax, the left side is the bottleneck: regardless of what lies farther right, the right boundary is at least rightMax, so the left bar's contribution is determined by leftMax. The symmetric argument applies when rightMax < leftMax.
Key insight
The side with the smaller running maximum cannot be helped by a taller unseen wall on the opposite side; its current maximum already caps the water.
Data structure model
| State | Meaning |
|---|---|
left, right | Unprocessed boundary indices |
leftMax | Tallest bar from the processed left side |
rightMax | Tallest bar from the processed right side |
answer | Water safely accumulated so far |
Micro-example
If leftMax = 2 and rightMax = 5, process left.
A left bar of height 1 adds 2 - 1 = 1, then left advances.
Pitfalls
- Comparing current bar heights instead of
leftMaxandrightMaxloses the running-boundary invariant. - Advancing both pointers can skip bars whose contribution is not yet safe.
- Update the relevant maximum before calculating that side's trapped water.
Recap
- Maintain maxima incrementally.
- Process the smaller maximum because it is the bottleneck.
- Each pointer moves inward once, producing a linear scan.
Key Idea: Two Running Maxima
The smaller maximum fixes the water level for its current pointer.
Left water level is at most 2.
Main Approach and Correctness
Learning goal: Translate the bottleneck argument into a one-pass algorithm.
Core idea
At every iteration, the unprocessed region lies between left and right. If leftMax <= rightMax, the current left bar has a guaranteed right boundary of at least rightMax, so its water is exactly leftMax - height[left] after updating leftMax. We can process it permanently and move inward. This preserves the invariant that every added amount is final.
Algorithm plan
- Set
left = 0,right = n - 1, and both running maxima to0. - While
left <= right, compareleftMaxandrightMax. - Process the side with the smaller maximum.
- Update that maximum, add its difference from the current bar, and advance the pointer.
while left <= right:
if left_max <= right_max:
left_max = max(left_max, height[left])
answer += left_max - height[left]
left += 1
else:
right_max = max(right_max, height[right])
answer += right_max - height[right]
right -= 1Micro-example
With leftMax = 2 and rightMax = 3, a left bar of height 0 contributes 2.
The right side is already tall enough that no future discovery can lower this left-side level.
Pitfalls
- Use
<=consistently in one branch; equality is safe for either side. - Do not separately scan for maxima inside the loop.
- Updating a maximum and adding
max - heightnaturally gives zero for a new tallest bar.
Recap
- One pointer advances per iteration.
- The invariant makes each contribution final when added.
- The algorithm uses constant auxiliary space.
Main Approach and Correctness
| Approach | Time | Space | Core view |
|---|---|---|---|
| Two pointers | O(n) | O(1) | Process the smaller running maximum |
| Monotonic stack | O(n) | O(n) | Fill bounded layers when a taller right wall appears |
Walkthrough: One Example End to End
Learning goal: Trace every pointer decision on a complete input.
Core idea
We use height = [0,1,0,2,1,0,1,3,2,1,2,1], which has answer 6. The pointers begin at both ends. The side with the smaller running maximum is processed, so the trace may move left several times and then right several times as the bottleneck changes.
Algorithm plan
- Track
left,right,leftMax,rightMax, andanswer. - When processing left, update
leftMaxand addleftMax - height[left]. - When processing right, update
rightMaxand addrightMax - height[right]. - Stop after the pointers meet.
Micro-example
At index 2, leftMax = 1 and height[2] = 0, so one unit is added.
Later, index 5 sees leftMax = 2, contributing two units; these are part of the final six.
Trace invariant
Every amount added is water whose limiting side is already known; no later bar can invalidate it.
Pitfalls
- The pointer position is not the same as the running maximum.
- A zero contribution is still a meaningful step: it confirms a boundary bar.
- Keep the running total visible to avoid losing contributions across direction changes.
Recap
- Left processing contributes at indices
2,4,5,6, and8. - The total becomes
1 + 1 + 2 + 1 + 1 = 6. - The next widget exposes each state transition.
Walkthrough: One Example End to End
End-to-end trace of the canonical example using running maxima.
Pitfalls and Edge Cases
Learning goal: Avoid the implementation mistake that destroys the linear-time guarantee.
Core idea
A tempting solution scans leftward and rightward from every index to recompute the tallest walls. Although the formula is correct, repeating those scans costs O(n²). The two-pointer solution maintains the maxima incrementally, so each bar is examined once.
Algorithm plan
- Initialize both maxima to
0, which handles edge bars and zero-height input naturally. - Advance exactly one pointer on each loop iteration.
- Use
max(currentMax, height[index])before adding water. - Test empty, short, monotonic, and flat arrays.
Common pitfall
Recomputing maxLeft and maxRight for every index is O(n²). Store the running maxima and update them as pointers move.
Micro-example
For [1,2,3], the running maxima rise with every bar, so the answer is 0.
For [3,2,1], the right maximum rises while processing from the right, also giving 0.
Pitfalls
- Forgetting to update the maximum before subtraction can produce negative water.
- Moving both pointers in one iteration can skip an unresolved bar.
- Arrays with fewer than three bars always trap zero water.
- The monotonic stack alternative uses
O(n)space; it is not the target approach here.
Recap
- Incremental state prevents repeated work.
- Monotonic or flat terrain is handled without special-case branches.
- Check pointer movement and maximum updates carefully.
Pitfalls and Edge Cases
Which implementation mistake turns this problem into O(n²)?
Complexity and Wrap-up
Learning goal: Explain why the two-pointer solution meets the target bounds.
Core idea
Each loop iteration advances left or decreases right; neither pointer ever moves backward. Therefore, there are at most n pointer advances and a constant amount of work per advance. The algorithm stores only a few scalar variables, unlike prefix/suffix arrays or a monotonic stack.
Algorithm plan
- Count each bar as processed once.
- Treat maximum updates, comparisons, and additions as constant-time operations.
- Report auxiliary space separately from the input array.
Micro-example
For n = 12, the walkthrough makes at most 12 pointer advances and returns 6.
Doubling n roughly doubles the work rather than quadrupling it.
Why it works
The smaller running maximum fixes the current water level, so every bar is finalized exactly once while the pointers converge.
Pitfalls
- Do not report
O(n)space for the input array when discussing auxiliary space. - A monotonic stack is linear time but uses
O(n)additional space. - The repeated-maxima approach has the right answer but misses the required performance target.
Recap
- Time:
O(n). - Auxiliary space:
O(1). - The canonical strategy is two pointers; the monotonic stack is a useful alternative perspective.
Complexity and Wrap-up
Final complexity breakdown for the two-pointer solution.
Now try it on your own problem
Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.
Build a lesson from any problem
