One of the most popular array problems you’ll encounter in coding interviews is Trapping Rain Water. At first glance, it looks like a geometry problem, but it’s really about identifying the tallest boundaries on both sides of every bar.
Let’s break it down step by step, starting with the simplest solution and gradually optimizing it.
Problem Statement
You’re given an array of non-negative integers where each value represents the height of a bar with a width of 1.
Your task is to calculate how much rainwater can be trapped after it rains.
Example
Input:
height = [0,2,0,3,1,0,1,3,2,1]
Output:
9Visualizing the bars makes it easier to see that water gets trapped in the valleys between taller bars.
Key Observation
For every position, the amount of water it can hold depends on:
- The tallest bar to its left
- The tallest bar to its right
The water level at any index is determined by the shorter of these two boundaries.
Mathematically:
water[i] = min(maxLeft, maxRight) - height[i]If this value is negative, we simply treat it as zero.
Solution 1: Brute Force
The most straightforward solution is to calculate the tallest bar on both sides for every index.
For each position:
- Scan left to find the maximum height.
- Scan right to find the maximum height.
- Add the trapped water.
from typing import List
class Solution:
def trap(self, height: List[int]) ->int:
if not height:
return 0
n = len(height)
res = 0
for i in range(n):
leftMax = rightMax = height[i]
for j in range(i):
leftMax = max(leftMax, height[j])
for j in range(i + 1, n):
rightMax = max(rightMax, height[j])
res += min(leftMax, rightMax) - height[i]
return resComplexity
- Time:
O(n²) - Space:
O(1)
Why is it slow?
For every element, we’re scanning the array twice:
- once to the left
- once to the right
This repeated work makes the algorithm quadratic.
Solution 2: Prefix and Suffix Maximum Arrays
Instead of repeatedly searching for the tallest bars, we can precompute them.
We’ll build two arrays:
prefix[i]→ tallest bar from the left up toisuffix[i]→ tallest bar from the right up toi
Building the Prefix Array
Height:
0 2 0 3 1 0 1 3 2 1
Prefix:
0 2 2 3 3 3 3 3 3 3Building the Suffix Array
Height:
0 2 0 3 1 0 1 3 2 1
Suffix:
3 3 3 3 3 3 3 3 2 1Now each position already knows its tallest boundary on both sides.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
n = len(height)
res = 0
prefix = [0] * n
suffix = [0] * n
prefix[0] = height[0]
for i in range(1, n):
prefix[i] = max(prefix[i - 1], height[i])
suffix[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
suffix[i] = max(suffix[i + 1], height[i])
for i in range(n):
res += min(prefix[i], suffix[i]) - height[i]
return resComplexity
- Time:
O(n) - Space:
O(n)
This eliminates repeated scanning, making the algorithm linear.
Solution 3: Two Pointers (Optimal)
Can we eliminate the extra arrays?
Yes.
Instead of storing prefix and suffix arrays, we can maintain:
leftMaxrightMax
while moving two pointers toward each other.
l r
↓ ↓
0 2 0 3 1 0 1 3 2 1The important insight is:
The side with the smaller maximum height determines how much water can be trapped at that moment.
Why?
Suppose:
leftMax = 3
rightMax = 7The water level on the left can never exceed 3, regardless of what happens further on the right. So we can safely process the left pointer.
Similarly, if:
leftMax = 8
rightMax = 4The right side becomes the limiting boundary, so we process the right pointer.
This lets us compute the answer in a single pass.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
l, r = 0, len(height) - 1
leftMax = height[l]
rightMax = height[r]
res = 0
while l < r:
if leftMax < rightMax:
l += 1
leftMax = max(leftMax, height[l])
res += leftMax - height[l]
else:
r -= 1
rightMax = max(rightMax, height[r])
res += rightMax - height[r]
return resComplexity
- Time:
O(n) - Space:
O(1)
This is the optimal solution.
Dry Run
Consider:
height = [0,2,0,3,1,0,1,3,2,1]Initially:
l = 0
r = 9
leftMax = 0
rightMax = 1Since leftMax < rightMax, we move the left pointer.
Eventually:
leftMax = 2When we encounter:
height = 0Water trapped becomes:
2 - 0 = 2Later:
leftMax = 3When another valley appears:
height = 1
water = 3 - 1 = 2Repeating this process across the array gives a total trapped water of:
9Comparison
Approach | Time | Space |
|---|---|---|
Brute Force |
|
|
Prefix & Suffix Arrays |
|
|
Two Pointers |
|
|
Final Thoughts
The Trapping Rain Water problem is an excellent example of how understanding the properties of a problem can lead to significant optimizations.
We started with a brute-force solution that repeatedly searched for boundaries, improved it by precomputing prefix and suffix maximums, and finally arrived at the elegant two-pointer approach that achieves linear time with constant extra space.
The biggest takeaway is recognizing that the smaller of the two maximum boundaries determines the water level. Once you internalize this insight, the optimal solution becomes much more intuitive.
This pattern of progressively refining a solution—from brute force to precomputation to two pointers—is common in coding interviews, making this problem a valuable one to master.