Solving tips
- Start from the per-cell formula: water[i] = min(maxLeft[i], maxRight[i]) - height[i], clamped at 0; everything else is how to compute those maxima efficiently.
- The clean O(n)/O(n) baseline is prefix and suffix max arrays; state it before optimizing to the O(1)-space two-pointer version.
- For O(1) space, walk both ends carrying running left_max and right_max, and always advance the side with the smaller bar since its running max is the binding min and the unseen side can only be taller.
- Pitfall: process the smaller side, not the larger; a monotonic stack (O(n) space) is the other O(n) option and needs careful width = i - left_wall - 1 and depth off the popped floor.
Problem
You are given an array height where height[i] is the height of a bar of width 1 standing at position i, forming an elevation profile. After it rains, water settles in the valleys between taller bars. Return the total number of unit squares of water the profile traps.
Water above position i rises to the level of the shorter of the tallest bar to its left and the tallest bar to its right β anything higher spills off the ends.
Examples
Example 1
Input: height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
Output: 6
One unit sits at index 2, one at index 4, two at index 5, one at index 6, one at index 9.
Example 2
Input: height = [4, 2, 0, 3, 2, 5]
Output: 9
The walls of height 4 and 5 trap 2 + 4 + 1 + 2 = 9 units over indices 1β4.
Example 3
Input: height = [3, 1, 2]
Output: 1
Index 1 holds water up to min(3, 2) = 2, i.e. 1 unit. Monotonic profiles trap nothing.
Constraints
1 <= height.length <= 2 * 10^4
0 <= height[i] <= 10^5
- An O(n^2) rescan per position is too slow; O(n) is expected, and O(1) space is the flourish.
Think about it first
Hint 1
Water above a single position depends on exactly two quantities. Write the formula for the water at index `i` before thinking about any algorithm.
Hint 2
`water[i] = min(max_left[i], max_right[i]) - height[i]` (clamped at 0). Both max-arrays can be precomputed in one sweep each. That's already O(n) β now try to drop the arrays.
Hint 3
Walk pointers in from both ends carrying running maxima. When `left_max < right_max`, the water level at the left pointer is decided by `left_max` alone β some wall of at least `right_max β₯ left_max` exists to the right, so the true right max can only be bigger and the min is already known. Settle that cell and move inward.
TL;DR
Two pointers carrying running maxima β O(n) time, O(1) space (prefix/suffix max arrays give O(n)/O(n); a monotonic stack also runs O(n)/O(n)).
Approach 1 β Brute force
For every position, rescan the whole array for the tallest bar on each side, then apply the water formula directly.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
total = 0
for i in range(n):
left_max = max(height[: i + 1])
right_max = max(height[i:])
total += min(left_max, right_max) - height[i]
return total
Complexity: O(n^2) time, O(1) space.
Each of up to 2 * 10^4 positions rescans up to 2 * 10^4 bars β ~4 * 10^8 operations, past the limit.
Approach 2 β Prefix/suffix maximum arrays
The insight: the water level over i is min(max_left[i], max_right[i]), and both quantities are classic prefix computations β each is derivable from its neighbor in O(1). Two sweeps precompute them; a third sweep sums the water.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
if n < 3:
return 0
left_max = [0] * n
left_max[0] = height[0]
for i in range(1, n):
left_max[i] = max(left_max[i - 1], height[i])
right_max = [0] * n
right_max[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
return sum(
min(left_max[i], right_max[i]) - height[i]
for i in range(n)
)
Walkthrough on height = [4, 2, 0, 3, 2, 5]:
left_max = [4, 4, 4, 4, 4, 5]
right_max = [5, 5, 5, 5, 5, 5]
- Water per index:
0, 2, 4, 1, 2, 0 β total 9. β
Complexity: O(n) time, O(n) space for the two arrays.
Approach 3 β Two pointers with running maxima
The insight: you never need both maxima exactly β only the smaller one. Keep pointers at both ends with running maxima left_max and right_max. When left_max < right_max, the bar producing right_max already stands somewhere right of the left pointer, so the true right-side maximum at that cell is β₯ right_max > left_max β the min is left_max, decided without ever scanning the right side. Settle the left cell and advance; mirror the argument on the other side.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
left, right = 0, len(height) - 1
left_max, right_max = 0, 0
total = 0
while left < right:
if height[left] < height[right]:
left_max = max(left_max, height[left])
total += left_max - height[left]
left += 1
else:
right_max = max(right_max, height[right])
total += right_max - height[right]
right -= 1
return total
Walkthrough on height = [3, 1, 2]:
| left | right | h[l] vs h[r] | action | water added | total |
|---|
| 0 (3) | 2 (2) | 3 β₯ 2 | right side: right_max = 2, add 2 - 2 = 0 | 0 | 0 |
| 0 (3) | 1 (1) | 3 β₯ 1 | right side: right_max stays 2, add 2 - 1 = 1 | 1 | 1 |
| 0 | 0 | β | pointers met | β | 1 β |
(Comparing height[left] < height[right] is equivalent to comparing the running maxima: the side with the smaller current bar is the side whose running max is the binding one.)
Complexity: O(n) time β each step retires one index. O(1) space.
Approach 4 β Monotonic stack (horizontal layers)
The insight: instead of summing water column by column, sum it layer by layer. Keep a stack of indices with decreasing heights β a monotonic stack, the classic structure for nearest-taller-element problems. When a bar taller than the stack top arrives, the top is a valley floor: itβs bounded by the new bar on the right and the next stack element on the left, so a horizontal slab of water can be accounted for and the floor popped.
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
stack: List[int] = [] # indices, heights strictly decreasing
total = 0
for i, h in enumerate(height):
while stack and height[stack[-1]] < h:
floor = stack.pop()
if not stack:
break # no left wall for this floor
left_wall = stack[-1]
width = i - left_wall - 1
depth = min(height[left_wall], h) - height[floor]
total += width * depth
stack.append(i)
return total
Walkthrough on height = [4, 2, 0, 3, 2, 5]:
- Push 0(4), 1(2), 2(0).
i = 3 (3): pop 2(0) β walls 1(2) and 3 β width 1, depth min(2,3) - 0 = 2 β +2. Pop 1(2) β walls 0(4) and 3 β width 2, depth min(4,3) - 2 = 1 β +2. Push 3. (total 4)
i = 4 (2): 2 < 3, just push 4. Stack: 0(4), 3(3), 4(2).
i = 5 (5): pop 4(2) β walls 3(3), width 1, depth 1 β +1. Pop 3(3) β walls 0(4), width 4, depth min(4,5) - 3 = 1 β +4. Pop 0(4) β stack empty, stop. (total 9) β
Complexity: O(n) time β every index is pushed and popped at most once. O(n) space for the stack.
Common pitfalls
- Clamping: with precomputed arrays that include
height[i] itself, min(left_max, right_max) - height[i] is never negative β but if you define the maxima as strictly-beside maxima, it can be; know which definition youβre using.
- Two-pointer side choice: you must process the side with the smaller bar; processing the larger side uses a running max that isnβt yet guaranteed to bound the cell.
- Stack version width/depth: width is
i - left_wall - 1 (exclusive of both walls) and depth subtracts the popped floor, not the wall β both off-by-ones are common.
- Edge profiles: monotonic or length-<3 inputs trap zero; make sure your loop bounds return 0 rather than crashing.
Pattern takeaway
The two-pointer version generalizes a powerful idea: when an answer at a position is min (or max) of a left-quantity and a right-quantity, you can often walk in from both ends and finalize whichever side is currently bound by the smaller value β the unseen side can only make the min argument stronger, never weaker. That βthe worse side is already decidedβ certificate is the same reasoning as Container With Most Water, and itβs the standard route from an O(n)-space prefix/suffix solution down to O(1).