Trapping Rain Water
Difficulty: Hard Source: NeetCode
Problem
Given
nnon-negative integers representing an elevation map where the width of each bar is1, compute how much water it can trap after raining.Example 1: Input:
height = [0,1,0,2,1,0,1,3,2,1,2,1]Output:6Example 2: Input:
height = [4,2,0,3,2,5]Output:9Constraints:
n == height.length1 <= n <= 2 * 10^40 <= height[i] <= 10^5
Prerequisites
Before attempting this problem, you should be comfortable with:
- Prefix/Suffix Arrays — precomputing running maximums from both ends
- Two Pointers — processing from both ends and choosing which side to advance
- Water Level Intuition — water at position
i=min(max_left, max_right) - height[i]
1. Brute Force
Intuition
For every bar, the water above it is determined by the shortest of the tallest walls on its left and its right. Specifically: water[i] = min(max_left[i], max_right[i]) - height[i]. In the brute force version, we compute those two maxima by scanning left and right from every position — leading to O(n²) time.
Algorithm
- Initialize
total = 0. - For each index
ifrom0ton - 1:- Scan left to find
max_left = max(height[0..i]). - Scan right to find
max_right = max(height[i..n-1]). - Add
min(max_left, max_right) - height[i]tototal.
- Scan left to find
- Return
total.
Solution
def trap(height):
n = len(height)
total = 0
for i in range(n):
max_left = max(height[:i + 1])
max_right = max(height[i:])
total += min(max_left, max_right) - height[i]
return total
print(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])) # 6
print(trap([4, 2, 0, 3, 2, 5])) # 9
Complexity
- Time:
O(n²) - Space:
O(1)
2. Prefix/Suffix Max Arrays
Intuition
We’re recomputing the same maxima over and over in the brute force. Precompute them once. Build max_left[i] = maximum height from index 0 to i, and max_right[i] = maximum height from index i to n-1. Then a single pass accumulates the water.
Algorithm
- Build
max_left: scan left to right,max_left[i] = max(max_left[i-1], height[i]). - Build
max_right: scan right to left,max_right[i] = max(max_right[i+1], height[i]). - For each
i, addmin(max_left[i], max_right[i]) - height[i]tototal.
Solution
def trap(height):
n = len(height)
max_left = [0] * n
max_right = [0] * n
max_left[0] = height[0]
for i in range(1, n):
max_left[i] = max(max_left[i - 1], height[i])
max_right[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
max_right[i] = max(max_right[i + 1], height[i])
total = 0
for i in range(n):
total += min(max_left[i], max_right[i]) - height[i]
return total
print(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])) # 6
print(trap([4, 2, 0, 3, 2, 5])) # 9
Complexity
- Time:
O(n) - Space:
O(n)
3. Two Pointers (Optimal)
Intuition
Can we avoid the extra arrays? Yes — with two pointers. The key observation: at any position, the water trapped depends on min(max_left, max_right). If the max we’ve seen from the left is smaller than the max we’ve seen from the right, the left side is the bottleneck — we can safely compute water for the left pointer without knowing the exact max_right (because whatever it is, it’s at least as large as max_right so far). Process whichever side has the smaller running maximum, advance that pointer, and keep track of max_left and max_right as you go.
Algorithm
- Initialize
left = 0,right = n - 1,max_left = 0,max_right = 0,total = 0. - While
left <= right:- If
max_left <= max_right:- Update
max_left = max(max_left, height[left]). - Add
max_left - height[left]tototal. - Increment
left.
- Update
- Else:
- Update
max_right = max(max_right, height[right]). - Add
max_right - height[right]tototal. - Decrement
right.
- Update
- If
- Return
total.
Solution
def trap(height):
left, right = 0, len(height) - 1
max_left, max_right = 0, 0
total = 0
while left <= right:
if max_left <= max_right:
max_left = max(max_left, height[left])
total += max_left - height[left]
left += 1
else:
max_right = max(max_right, height[right])
total += max_right - height[right]
right -= 1
return total
print(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])) # 6
print(trap([4, 2, 0, 3, 2, 5])) # 9
print(trap([3, 0, 2, 0, 4])) # 7
Complexity
- Time:
O(n) - Space:
O(1)
Common Pitfalls
Updating max_left/max_right before subtracting. You must update the running max first (max_left = max(max_left, height[left])), then compute the water (max_left - height[left]). If you subtract first, you could get a negative value for the bar that sets a new maximum.
Confusing which pointer to advance. Advance the side with the smaller running max — that’s the bottleneck side whose water level is already determined. The other side’s max can only be higher, so the water formula is safe to apply.
Using left < right instead of left <= right. When left == right, both pointers are on the same bar and need to be processed. Using < skips the middle element and can undercount water in odd-length arrays.
Negative water values. Each bar can only trap water if it’s lower than both surrounding walls. The formula min(max_left, max_right) - height[i] is always non-negative because both maxima include height[i] itself. If you see a negative contribution, you’ve got the max computation wrong.