Problem
Given an array of bar heights, calculate how many units of water get trapped between the bars after rain falls. Water at any position fills up to the level of the shorter of the two tallest bars on either side.
Example:
- Input:
height = [0,1,0,2,1,0,1,3,2,1,2,1] - Output:
6 - Explanation: The valleys between the taller bars trap 6 units total โ 1 unit at index 2, 1 at index 4, 2 at index 5, 1 at index 6, and 1 at index 9.
Counter-example: height = [3,0,3] traps 3 units (the pit is 3 wide, bounded by walls of height 3 on both sides), but height = [3,0,1] traps only 1 unit โ the right wall at height 1 is the bottleneck, not the left.
Intuition
Every bar's water level is determined by the shorter of the tallest walls to its left and right โ it doesn't matter how close or far those walls are. The brute-force insight is to find these two maxima for every position; the optimization is to avoid rescanning the array for each bar by caching those maxima. The two-pointer technique goes further: by always advancing the side with the smaller maximum, you can compute each bar's contribution on the fly with no extra storage.
Approach 1 โ Brute Force
For each bar, scan left to find the tallest bar on that side and scan right to find the tallest on the other side. The water at that position is the shorter wall minus the bar height.
Steps:
- For each index
i, initializeleft_maxandright_maxto 0 - Scan indices 0 through
ito find the tallest bar to the left (includingi) - Scan indices
ithroughn-1to find the tallest bar to the right (includingi) - Add
min(left_max, right_max) - height[i]to the running total
1def trap(height):
2 total_water = 0
3 n = len(height)
4
5 for i in range(n):
6 left_max = 0
7 for j in range(i + 1): # scan left boundary up to i
8 left_max = max(left_max, height[j])
9 right_max = 0
10 for j in range(i, n): # scan right boundary from i onward
11 right_max = max(right_max, height[j])
12 total_water += min(left_max, right_max) - height[i]
13
14 return total_water- Time: O(nยฒ) โ for each of n bars, two inner scans each touch up to n elements
- Space: O(1) โ only a handful of integer variables regardless of input size
Approach 2 โ Prefix Max Arrays
Pre-compute left_max[i] (the tallest bar from index 0 through i) and right_max[i] (the tallest from i through n-1) in two separate passes. Then a single final pass reads both arrays to compute each bar's water in O(1).
Steps:
- Build
left_maxleft to right:left_max[i] = max(left_max[i-1], height[i]) - Build
right_maxright to left:right_max[i] = max(right_max[i+1], height[i]) - For each index
i, addmin(left_max[i], right_max[i]) - height[i]to total
1def trap(height):
2 n = len(height)
3 left_max = [0] * n
4 right_max = [0] * n
5
6 left_max[0] = height[0]
7 for i in range(1, n):
8 left_max[i] = max(left_max[i - 1], height[i])
9
10 right_max[n - 1] = height[n - 1]
11 for i in range(n - 2, -1, -1):
12 right_max[i] = max(right_max[i + 1], height[i])
13
14 total_water = 0
15 for i in range(n):
16 # water level is the shorter ceiling; subtract the bar itself
17 total_water += min(left_max[i], right_max[i]) - height[i]
18
19 return total_water- Time: O(n) โ three linear passes over the array
- Space: O(n) โ two auxiliary arrays each of length n
Approach 3 โ Two Pointers
Converge two pointers from opposite ends. The key invariant: whichever side has the smaller maximum is the bottleneck for that pointer's current bar โ water there is fully determined by that max alone, regardless of what's on the other side.
Steps:
- Initialize
left = 0,right = n-1,left_max = 0,right_max = 0,total_water = 0 - While
left < right, compareheight[left]toheight[right] - If
height[left] <= height[right]: updateleft_max; ifheight[left] < left_max, accumulate water; advanceleft - Otherwise: update
right_max; ifheight[right] < right_max, accumulate water; advanceright - Return
total_water
1def trap(height):
2 left, right = 0, len(height) - 1
3 left_max = right_max = 0
4 total_water = 0
5
6 while left < right:
7 if height[left] <= height[right]:
8 if height[left] >= left_max:
9 left_max = height[left] # new tallest on the left; no water here
10 else:
11 total_water += left_max - height[left] # left_max is the guaranteed ceiling
12 left += 1
13 else:
14 if height[right] >= right_max:
15 right_max = height[right] # new tallest on the right; no water here
16 else:
17 total_water += right_max - height[right]
18 right -= 1
19
20 return total_water- Time: O(n) โ each bar is visited exactly once as the two pointers converge
- Space: O(1) โ only four integer variables regardless of input size
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force | O(nยฒ) | O(1) | Never in practice; useful for establishing the core insight in an interview |
| Prefix Max Arrays | O(n) | O(n) | Default choice โ three clean passes, easiest to explain and debug |
| Two Pointers | O(n) | O(1) | When the interviewer pushes for O(1) space, or in memory-constrained environments |
Common Mistakes
- Thinking only adjacent bars form the walls โ water at index
iis bounded by the tallest bar anywhere to its left and the tallest anywhere to its right, not just immediate neighbors; a short bar right next toidoes not reduce how much wateriholds. - Off-by-one in the prefix arrays โ
left_max[i]must equalmax(height[0], ..., height[i])inclusive; if you compute it asmax(height[0], ..., height[i-1])only, bars at peaks incorrectly accumulate positive water instead of zero. - Moving the wrong pointer in the two-pointer approach โ always advance the pointer whose side has the smaller maximum, because that max is the ceiling; if you advance the larger side, the invariant breaks and you lose track of the actual bottleneck.
- Using
left <= rightin the two-pointer loop โ whenleft == right, the single remaining bar holds no water; processing it is harmless for the total but iterating past it (right - 1after a right-side step on a one-element remainder) can access index -1 in some implementations. - Expecting
min(left_max, right_max) - height[i]to go negative โ since each max array is built to includeheight[i]itself, the ceiling is always at least as tall as the current bar, so the difference is always โฅ 0; adding amax(0, ...)guard signals a misunderstood boundary condition rather than defensive coding.
Related Problems
container-with-most-waterโ same two-pointer shrink-from-outside pattern, maximizing the enclosed area rather than summing trapped unitslargest-rectangle-in-histogramโ bounding walls on bars; a stack tracks the extent each bar can extend as the rectangle's heightproduct-of-array-except-selfโ left and right prefix passes to compute per-element answers without rescanning, the same structural idea as Approach 2subarray-sum-equals-kโ prefix sums that answer range queries in O(1), the same caching optimization applied to sums instead of maximamaximum-subarrayโ running maximum updated in a single left-to-right pass, analogous to howleft_maxevolves during the two-pointer walk