Start learning free

LESSON 371 OF 442 · DYNAMIC PROGRAMMING

Trapping Rain Water in TypeScript: Prefix and Suffix Maximums

Practice O(n) Read the full explanation

Given n non-negative integers representing an elevation map where the width of each bar is 1, 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: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

Example 2:

Input: height = [4,2,0,3,2,5]
Output: 9

 

Constraints:

  • n == height.length
  • 1 <= n <= 3 * 104
  • 0 <= height[i] <= 104
STEP 1 OF 12
    Ctrl + Enter to check

    TYPESCRIPT · DYNAMIC PROGRAMMING

    Trapping Rain Water in TypeScript, explained

    Problem overview

    The method trap receives height, an array of non-negative integers describing an elevation map. Each number is the height of a bar, and every bar is 1 unit wide. After rain, water collects in the dips between taller bars. The method returns how many units of water stay trapped.

    For [4, 2, 0, 3, 2, 5] the answer is 9. The two outer bars, 4 and 5, are walls. Between them, the bars 2, 0, 3 and 2 hold water up to level 4, the lower of the walls 4 and 5, as the next section explains. That is 2 + 4 + 1 + 2 = 9 units. The bars have at least one element and at most 30,000, each up to 10,000 high.

    Approach: each bar holds its own column of water

    Instead of thinking about whole pools, ask how much water sits directly above one bar. Water above bar i rises to the lower of two heights: the tallest bar anywhere to its left (including itself) and the tallest bar anywhere to its right (including itself). Water at a higher level would spill over the lower wall. The bar itself takes up the bottom of the column, so the water above it is

    min(tallest on the left, tallest on the right) - height[i]

    The total is the sum of this value over all bars. Because both maximums include bar i, the minimum is at least height[i], so the difference is never negative.

    The straightforward way scans left and right from every bar to find the two maximums, which takes O(n) per bar and O(n²) overall. This lesson precomputes them instead. Two arrays do it:

    • leftMax[i] is the tallest bar among positions 0 to i.
    • rightMax[i] is the tallest bar among positions i to n - 1.

    Each entry builds on its neighbour: leftMax[i] is the larger of leftMax[i - 1] and height[i]. Reusing earlier answers like that is dynamic programming.

    For [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] the arrays are:

    height   0 1 0 2 1 0 1 3 2 1 2 1
    leftMax  0 1 1 2 2 2 2 3 3 3 3 3
    rightMax 3 3 3 3 3 3 3 3 2 2 2 1
    water    0 0 1 0 1 2 1 0 0 1 0 0   (total 6)

    Step by step

    1. Store the length in a const named n, since every loop uses it.
    2. Return 0 for no bars. The next lines read height[0], which needs at least one bar.
    3. Create leftMax with n zeros and set leftMax[0] to height[0], since nothing is to the left of the first bar.
    4. Loop from index 1 up to n - 1. Index 0 is already set.
    5. Take the larger of leftMax[i - 1] and height[i] with Math.max.
    6. Create rightMax the same way, and set its last element to the last bar.
    7. Loop backwards from n - 2 down to 0.
    8. Take the larger of rightMax[i + 1] and height[i].
    9. Create result with let, starting at 0.
    10. Loop over every bar.
    11. Add the column's water: the smaller of leftMax[i] and rightMax[i], minus height[i].
    12. Return result.

    The solution in TypeScript

    class Solution {
        trap(height: number[]): number {
            const n = height.length;
            if (n === 0) return 0;
            const leftMax: number[] = new Array(n).fill(0);
            leftMax[0] = height[0];
            for (let i = 1; i < n; i++) {
                leftMax[i] = Math.max(leftMax[i - 1], height[i]);
            }
            const rightMax: number[] = new Array(n).fill(0);
            rightMax[n - 1] = height[n - 1];
            for (let i = n - 2; i >= 0; i--) {
                rightMax[i] = Math.max(rightMax[i + 1], height[i]);
            }
            let result = 0;
            for (let i = 0; i < n; i++) {
                result += Math.min(leftMax[i], rightMax[i]) - height[i];
            }
            return result;
        }
    }
    

    The TypeScript details worth knowing:

    • new Array(n).fill(0). new Array(n) creates an array of length n with empty slots, and fill(0) writes a 0 into each. Without the fill, the slots would be empty instead of 0. Here every slot is overwritten anyway, but a filled array is the safe habit, and it keeps the array a plain list of numbers.
    • The annotation number[] is only for the compiler and is erased in the JavaScript. new Array(n) is typed any[], and the annotation gives the variable its precise type.
    • Math.max and Math.min take any number of arguments and return a plain number. Here each call has two.
    • No overflow worry. A number is a double and holds integers exactly up to 2^53. The most water possible is about 30,000 × 10,000 = 300 million, far below that.
    • const arrays can still change. leftMax[i] = ... changes the contents, which const allows. Only reassigning the variable itself is forbidden.

    Complexity

    Time: O(n). There are three passes over the bars, and each does a constant amount of work per bar.

    Space: O(n). leftMax and rightMax each hold n numbers. A two-pointer version needs only O(1) extra space, but it takes more care to see why it is correct. The arrays make the idea easy to see.

    Edge cases and common mistakes

    • One or two bars. [3] gives 0, and so does any array with fewer than three bars, since water needs a wall on each side.
    • Monotonic bars. [1, 2, 3] gives 0: each bar is its own left maximum, so its column has nothing above it.
    • A single pit. [3, 0, 3] gives 3. The middle bar has both maximums equal to 3.
    • Uneven walls. [2, 0, 2, 0, 1] gives 3: the pit between the 2s holds 2 units, and the pit between 2 and 1 holds 1 unit, since the lower wall, 1, sets that level.
    • Using only one maximum. Subtracting height[i] from leftMax[i] alone ignores the right wall and counts water that would run off.
    • Using max instead of min. Water can't rise above the lower wall, so using the taller one overcounts.
    • Starting the right loop at n - 1. That would read rightMax[n], which is undefined, and Math.max(undefined, x) is NaN. The whole array after it would be corrupted.

    Testing

    • [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] gives 6.
    • [4, 2, 0, 3, 2, 5] gives 9.
    • [3, 0, 3] gives 3.
    • [1, 2, 3] gives 0.
    • [3] gives 0.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free