Topics › Stacks and Queues › Monotonic Stack/Queue Problems [VVV. Imp]
Trapping Rainwater
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array height of n non-negative integers. It describes an elevation map: bar i is a column of width 1 and height height[i]. After it rains, water collects in the dips between taller bars. Return the total amount of water trapped, measured in unit squares.
Constraints (LeetCode 42): 1 ≤ n ≤ 2 × 10^4, 0 ≤ height[i] ≤ 10^5. The Java methods return a long so that they stay correct even for heights near 2 × 10^9, where the total can exceed an int.
Example 1
height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
answer = 6 // 1 unit above index 2, 4 units over indices 4-6, 1 unit above index 9
Example 2
height = [4, 2, 0, 3, 2, 5]
answer = 9 // the 4 and the 5 hold water at level 4: 2 + 4 + 1 + 2
Example 3
height = [5, 4, 1, 2]
answer = 1 // only the bar of height 1 holds water, filled up to the 2 on its right
The formula behind every approach. Look at one column i on its own. Water above it can rise only as high as the lower of two walls: the tallest bar anywhere on its left and the tallest bar anywhere on its right. Above that level the water would spill over the shorter wall. So
water[i] = min(maxLeft(i), maxRight(i)) - height[i]
where both maxima include height[i] itself. Including it means the result is never negative: a bar taller than everything on one side simply holds 0. The answer is the sum of water[i] over all columns. The four approaches below differ only in how quickly they find the two maxima.
Approach 1: Brute force
Intuition. Apply the formula literally. For each column, scan everything to its left for the tallest bar, scan everything to its right for the tallest bar, and add the difference. Each column costs a full pass over the array, so the total work is quadratic.
Algorithm.
- For every index
i: leftMax= maximum ofheight[0..i],rightMax= maximum ofheight[i..n-1].- Add
min(leftMax, rightMax) - height[i]to the total.
static long bruteForce(int[] height) {
int n = height.length;
long water = 0;
for (int i = 0; i < n; i++) {
int leftMax = 0, rightMax = 0;
for (int j = 0; j <= i; j++) leftMax = Math.max(leftMax, height[j]);
for (int j = i; j < n; j++) rightMax = Math.max(rightMax, height[j]);
water += Math.min(leftMax, rightMax) - height[i];
}
return water;
}
Complexity. Time O(n^2): each of the n columns scans n bars. Space O(1).
Every column recomputes maxima that its neighbour has just computed. Removing that repetition is the whole game.
Approach 2: Better, prefix and suffix maxima
Intuition. maxLeft(i) is a running maximum from the left, so maxLeft(i) = max(maxLeft(i - 1), height[i]). Likewise maxRight(i) = max(maxRight(i + 1), height[i]). Fill both arrays with one sweep each, then apply the formula in a third sweep.
Algorithm.
- If the array is empty, return
0. leftMax[0] = height[0]; fori = 1..n-1,leftMax[i] = max(leftMax[i-1], height[i]).rightMax[n-1] = height[n-1]; fori = n-2..0,rightMax[i] = max(rightMax[i+1], height[i]).- Sum
min(leftMax[i], rightMax[i]) - height[i].
static long prefixSuffix(int[] height) {
int n = height.length;
if (n == 0) return 0;
int[] leftMax = new int[n], rightMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; i++) leftMax[i] = Math.max(leftMax[i - 1], height[i]);
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) rightMax[i] = Math.max(rightMax[i + 1], height[i]);
long water = 0;
for (int i = 0; i < n; i++) water += Math.min(leftMax[i], rightMax[i]) - height[i];
return water;
}
Complexity. Time O(n): three linear sweeps. Space O(n) for the two arrays.
This is the version to reach for first in an interview: it follows directly from the formula and is easy to get right.
Approach 3: Monotonic stack, filling layer by layer
Intuition. The first two approaches measure water in vertical strips, one column at a time. You can also measure it in horizontal slabs. Walk left to right and keep a stack of indices whose heights never increase from bottom to top: these are bars still waiting for a taller bar on their right to close a basin.
When height[i] is taller than the bar on top of the stack, that top bar is the bottom of a dip. Pop it. The new top is the nearest bar on the left that is at least as tall as the bottom, which is the left wall, and i is the right wall. Between them lies a rectangle of water:
- width
= i - left - 1(the columns strictly between the walls), - depth
= min(height[left], height[i]) - height[bottom](from the bottom up to the lower wall).
Keep popping while height[i] is still taller than the top, adding one slab per pop. Each slab sits on top of the previous one, so together they fill the basin to the level of the lower wall. If the stack becomes empty after a pop, there is no left wall and that water spills away.
This is the Next Greater Element stack in disguise: a bar is popped exactly when its next greater bar to the right arrives, and the bar underneath it is its previous bar of at least the same height.
Algorithm.
- Create an empty stack of indices.
- For each
ifrom left to right:- While the stack is not empty and
height[i] > height[top]: popbottom; if the stack is now empty, stop; otherwise letleftbe the top and add(i - left - 1) × (min(height[left], height[i]) - height[bottom]). - Push
i.
- While the stack is not empty and
- Return the total.
static long monotonicStack(int[] height) {
long water = 0;
Deque<Integer> st = new ArrayDeque<>(); // indices; heights never increase from bottom to top
for (int i = 0; i < height.length; i++) {
while (!st.isEmpty() && height[i] > height[st.peek()]) {
int bottom = st.pop();
if (st.isEmpty()) break; // no wall on the left: this layer spills out
int left = st.peek();
long width = i - left - 1;
long depth = Math.min(height[left], height[i]) - height[bottom];
water += width * depth;
}
st.push(i);
}
return water;
}
Dry run on [4, 2, 0, 3, 2, 5] (stack shows indices, bottom first):
| i | height[i] | pops and slabs added | total | stack after push |
|---|---|---|---|---|
| 0 | 4 | none | 0 | 0 |
| 1 | 2 | none | 0 | 0, 1 |
| 2 | 0 | none | 0 | 0, 1, 2 |
| 3 | 3 | bottom 2, left 1: width 1, depth 2 → 2; bottom 1, left 0: width 2, depth 1 → 2 | 4 | 0, 3 |
| 4 | 2 | none | 4 | 0, 3, 4 |
| 5 | 5 | bottom 4, left 3: 1 × 1 → 1; bottom 3, left 0: 4 × 1 → 4; bottom 0, no left wall | 9 | 5 |
Complexity. Time O(n): every index is pushed once and popped at most once. Space O(n) for the stack, for example on a falling terrain where nothing is popped.
Equal heights need no special case. In [2, 1, 1, 2] the second 1 is popped with the first 1 as its left wall, giving depth 0; the next pop then uses the left 2 as the wall and adds a slab of width 2 and depth 1.
Approach 4: Optimal, two pointers
Intuition. We do not need both maxima exactly, only the smaller of the two. Put one pointer at each end and keep leftMax, the tallest bar passed by the left pointer, and rightMax, the tallest bar passed by the right pointer.
Suppose height[l] <= height[r]. Then the right side of l certainly has a wall at least as tall as height[r]. Every bar the left pointer has passed was passed while it was no taller than some bar at or beyond the current r, so leftMax is also no taller than the true maxRight(l). That means the lower wall for column l is on its left, and its water is max(leftMax, height[l]) - height[l], known right now, without ever learning maxRight(l) exactly. Process l and move it inward. The mirror argument applies when height[r] < height[l].
The pointers meet at one of the tallest bars, which holds no water, so the loop can stop at l < r.
Algorithm.
l = 0,r = n - 1,leftMax = rightMax = 0.- While
l < r:- If
height[l] <= height[r]: ifheight[l] >= leftMax, updateleftMax; otherwise addleftMax - height[l]. Thenl++. - Otherwise do the same on the right with
rightMax, thenr--.
- If
- Return the total.
static long optimal(int[] height) {
int l = 0, r = height.length - 1;
int leftMax = 0, rightMax = 0;
long water = 0;
while (l < r) {
if (height[l] <= height[r]) { // the right side has a wall at least this tall
if (height[l] >= leftMax) leftMax = height[l];
else water += leftMax - height[l];
l++;
} else { // the left side has a wall taller than this
if (height[r] >= rightMax) rightMax = height[r];
else water += rightMax - height[r];
r--;
}
}
return water;
}
Dry run on [5, 4, 1, 2]:
| l | r | compare | action | leftMax | rightMax | total |
|---|---|---|---|---|---|---|
| 0 | 3 | 5 > 2 | right side: 2 is a new max | 0 | 2 | 0 |
| 0 | 2 | 5 > 1 | right side: add 2 - 1 | 0 | 2 | 1 |
| 0 | 1 | 5 > 4 | right side: 4 is a new max | 0 | 4 | 1 |
| 0 | 0 | stop | 1 |
Complexity. Time O(n): each step moves one pointer one position. Space O(1).
Edge cases and pitfalls
- Fewer than three bars. No basin can form.
prefixSuffixmust return early on an empty array, because it readsheight[0]; the two-pointer loop simply never runs. - Monotonic terrain.
[1, 2, 3, 4]and[4, 3, 2, 1]both hold0: water spills off the low end. - Flat-bottomed basins.
[2, 1, 1, 2]holds2. The stack approach handles it through a zero-depth slab, as shown above. - Overflow. One column holds at most
max height, which fits in anint, but the total does not have to:[2000000000, 0, 0, 2000000000]holds4 × 10^9. Accumulate inlong, and in the stack approach computewidth × depthinlong. - Taking
maxinstead ofmin. Water is limited by the lower wall. Using the higher one is the most common slip with the formula. - Excluding the bar itself from the maxima. If
maxLeftandmaxRightdo not includeheight[i], the formula can go negative and must be clamped at0.
Related problems
- Next Greater Element is the stack pattern that Approach 3 is built on.
- Largest rectangle in a histogram uses the same “pop when a bar is closed off” logic, measuring rectangles under the bars instead of water above them.
- Container with most water uses the same “move the pointer at the lower wall” argument.
- Sum of Subarray Minimums uses previous and next smaller elements to count what each element contributes.
- Stock span problem is another previous-greater-element question solved with a monotonic stack.