Problem overview
The function trap receives height, an IntArray that describes an elevation map. Each element is the height of a bar of width 1. After it rains, water collects in the dips between taller bars. The function returns how many units of water stay trapped.
For [4, 2, 0, 3, 2, 5] the answer is 9. Above the bar of height 2 at index 1 the water rises to level 4, because the tallest bar on its left is 4 and the tallest on its right is 5, and the lower of the two sets the level. That column holds 2 units. The columns at indices 2, 3 and 4 hold 4, 1 and 2 units, which makes 9 together. The two end bars hold nothing.
The array can hold up to 30,000 bars, each up to 10,000 high.
Approach: water above a bar depends on two walls
Look at a single bar. Water above it is held in by the tallest bar somewhere on its left and the tallest somewhere on its right. The water level can only rise to the lower of those two walls, and the bar itself already fills the bottom of the column. So the water above bar i is:
min(tallest bar at or left of i, tallest bar at or right of i) - height[i]
Both maxima include bar i, so the result is never negative. For a bar that is itself the tallest on one side, the formula gives 0.
The brute-force way finds those two maxima by scanning left and right from every bar, which is O(n²) in total. But the maximum up to bar i is either the maximum up to bar i - 1 or bar i itself, so each entry can reuse the one before it. That reuse is the dynamic programming in this lesson. Precomputing one array for each direction makes every bar's answer a constant-time lookup.
For [4, 2, 0, 3, 2, 5] the two arrays are:
leftMax = [4, 4, 4, 4, 4, 5]
rightMax = [5, 5, 5, 5, 5, 5]
The smaller value at each index is 4, 4, 4, 4, 4, 5. Subtracting the heights 4, 2, 0, 3, 2, 5 gives 0, 2, 4, 1, 2, 0, and these add up to 9.
Step by step
- Store the number of bars in a
valnamedn. Every loop uses it. - Return 0 if
n == 0. No bars hold no water, and the next lines readheight[0], which needs at least one bar. - Create
leftMaxas anIntArrayofnzeros, and setleftMax[0]toheight[0]. Nothing comes before the first bar, so the tallest bar up to it is the bar itself. - Loop
iover1 until n. Index 0 is already set. - Set
leftMax[i]tomaxOf(leftMax[i - 1], height[i]): the tallest bar up toiis the tallest before it or bariitself. - Create
rightMaxthe same way, withrightMax[n - 1]set toheight[n - 1]. - Loop
iovern - 2 downTo 0. This range counts down and includes both ends. The last index is already set, so the loop starts one before it. - Set
rightMax[i]tomaxOf(rightMax[i + 1], height[i]). It is the mirror image of the first loop. - Create
resultwithvar, starting at 0. - Loop
iover0 until nto visit each bar once. - Add the water above bar
i:minOf(leftMax[i], rightMax[i]) - height[i]. - Return
result.
The solution in Kotlin
class Solution {
fun trap(height: IntArray): Int {
val n = height.size
if (n == 0) return 0
val leftMax = IntArray(n)
leftMax[0] = height[0]
for (i in 1 until n) {
leftMax[i] = maxOf(leftMax[i - 1], height[i])
}
val rightMax = IntArray(n)
rightMax[n - 1] = height[n - 1]
for (i in n - 2 downTo 0) {
rightMax[i] = maxOf(rightMax[i + 1], height[i])
}
var result = 0
for (i in 0 until n) {
result += minOf(leftMax[i], rightMax[i]) - height[i]
}
return result
}
}
The Kotlin details worth a closer look:
IntArrayis filled with zeros.IntArray(n)creates an array ofnprimitiveintvalues, all 0. It is fixed in size, and unlikeList<Int>it doesn't box each number.height.size. Arrays have asizeproperty, not alengthor asize()method.- Ranges.
1 until nexcludesn, so it covers the indices 1 ton - 1. Whennis 1, it is empty.n - 2 downTo 0counts down and includes both ends. Whennis 1, it starts at -1 and is also empty. A range written0..nwould includenand read past the end of the array. maxOfandminOf. These are top-level functions of the standard library that take two values. They need no import.- The sum fits in an
Int. The most water the constraints allow is under 30,000 times 10,000, or 300 million. That is below theIntlimit of about 2.1 billion, soIntarithmetic can't overflow here. if (n == 0) return 0. A single-lineifwithout braces is fine for one statement.
Complexity
Time: O(n). There are three passes over the array, one for each loop, and each does constant work per bar.
Space: O(n). The two helper arrays have n elements each. A two-pointer method that moves inward from both ends gets the same answer in O(1) extra space, but this lesson builds the arrays because each step is easy to see and check.
Edge cases and common mistakes
- A single bar.
[5]has no walls around it, and the function returns 0. Both maxima equal the bar, so the difference is 0. - Sorted bars.
[1, 2, 3]and[5, 4, 3, 2, 1]both return 0. Water needs a taller bar on each side. - A flat map. Equal heights, like
[10000, 10000, ...], hold nothing. - A deep pit.
[3, 0, 3]returns 3. With 30,000 bars where only the two ends are 10,000 high and the rest are 0, the answer is 299,980,000, which still fits in anInt. - Using only one maximum. With
leftMaxalone, water would be counted over bars that have no right wall. Both walls matter, and the lower one decides. - Forgetting to subtract the bar. The column's capacity counts from the ground, but the bar already fills part of it. Subtract
height[i]. - Starting the loops at the wrong index. The first entry of each array is set separately. Looping from 0 in
leftMaxwould readleftMax[-1]and throw anArrayIndexOutOfBoundsException.
Testing
[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]gives6.[4, 2, 0, 3, 2, 5]gives9.[5]gives0.[1, 2, 3]gives0.[3, 0, 3]gives3.[5, 4, 3, 2, 1]gives0.
