Start learning free

LESSON 372 OF 443 · DYNAMIC PROGRAMMING

Trapping Rain Water in Swift: Dynamic Programming with Prefix Maxima

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

    SWIFT · DYNAMIC PROGRAMMING

    Trapping Rain Water in Swift, explained

    Problem overview

    The method trap receives height, an array of non-negative integers. Each value is the height of a bar of width 1, standing side by side. 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. Above the bar of height 2 at index 1, water rises to level 4 (the wall on the left is 4, the wall on the right is 5), which holds 2 units. Above the bar of height 0 it also rises to 4, giving 4 units. Above 3 it holds 1 unit, and above the second 2 it holds 2 units. Together: 2 + 4 + 1 + 2 = 9. The bars at both ends hold nothing, since nothing stands beyond them.

    Approach: water above each bar

    Think about one bar at a time. The water above bar i rises until it would spill over the lower of the two walls around it: the tallest bar to its left and the tallest bar to its right (both counting bar i itself). So the water above it is

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

    Adding this over all bars gives the answer. The straightforward way to find the two maxima scans left and right from every bar, which is O(n²). But the tallest bar up to i follows from the tallest up to i - 1: it is the larger of that value and height[i]. Computing it for every position in one pass from the left, and likewise from the right, takes O(n) time and gives each lookup in constant time. Storing earlier results to reuse them is the dynamic programming here. A two-pointer version needs no arrays, but this lesson uses the two arrays because they make the idea easy to see.

    For [4, 2, 0, 3, 2, 5] the left maxima are [4, 4, 4, 4, 4, 5] and the right maxima are [5, 5, 5, 5, 5, 5]. The smaller of the two at each index is [4, 4, 4, 4, 4, 5], and subtracting the heights gives [0, 2, 4, 1, 2, 0], which adds up to 9.

    Step by step

    1. Store the number of bars in n, because every loop below uses it.
    2. Return 0 when there are no bars. The next lines read height[0], which needs at least one bar. Swift requires braces around the body, even for one statement.
    3. Create leftMax as n zeros and set its first element to height[0]: the tallest bar up to the first bar is the bar itself.
    4. Loop over 1..<n. Index 0 is already filled.
    5. Fill each entry with the larger of the previous entry and height[i].
    6. Create rightMax the same way and set its last element to height[n - 1].
    7. Loop backward with stride(from: n - 2, through: 0, by: -1), since the last entry is already set.
    8. Fill each entry with the larger of the entry after it and height[i].
    9. Start result at 0 with var.
    10. Loop over every bar, 0..<n.
    11. Add the water above bar i: the smaller of leftMax[i] and rightMax[i], minus height[i]. Both maxima include the bar itself, so the difference is never negative.
    12. Return result.

    The solution in Swift

    class Solution {
        func trap(_ height: [Int]) -> Int {
            let n = height.count
            if n == 0 { return 0 }
            var leftMax = [Int](repeating: 0, count: n)
            leftMax[0] = height[0]
            for i in 1..<n {
                leftMax[i] = max(leftMax[i - 1], height[i])
            }
            var rightMax = [Int](repeating: 0, count: n)
            rightMax[n - 1] = height[n - 1]
            for i in stride(from: n - 2, through: 0, by: -1) {
                rightMax[i] = max(rightMax[i + 1], height[i])
            }
            var result = 0
            for i in 0..<n {
                result += min(leftMax[i], rightMax[i]) - height[i]
            }
            return result
        }
    }
    

    Swift details worth knowing:

    • Creating an array of zeros. [Int](repeating: 0, count: n) builds an array with n elements, which the loops then overwrite. Writing leftMax[i] before the array has that many elements would crash, because Swift checks array bounds at run time.
    • Ranges. 1..<n excludes n, so it covers indices 1 to n - 1. For n = 1 it is empty and the loop does nothing. A range whose start is above its end, such as 1..<0, is not empty in Swift: creating it crashes the program. That is why the check for n == 0 comes first.
    • Counting down. 0..<n can't run backward. stride(from:through:by:) includes the end value (through), and by: -1 counts down. For n = 1 the loop starts at -1, so it makes no iterations, which is correct since rightMax[0] is already set.
    • min and max. These are global functions taking two values, as in max(leftMax[i - 1], height[i]).
    • Integer overflow. Swift's Int is 64 bits and overflow stops the program rather than wrapping. Here the largest possible total is about 3 * 10^4 bars times 10^4 height, far below the limit.

    Complexity

    Time: O(n). There are three separate loops over the bars, each with constant work per step.

    Space: O(n). The two arrays each hold n integers. The two-pointer version brings this down to O(1), at the price of a trickier invariant.

    Edge cases and common mistakes

    • One or two bars. [5] traps nothing: both maxima equal the bar, so the difference is 0. Two bars can't hold water either, since there is nothing between them.
    • Monotonic heights. [1, 2, 3] and [3, 2, 1] both give 0: on a staircase the water always spills over the lower side.
    • A single dip. [2, 0, 2] gives 2.
    • Using only one side. Taking the left maximum alone, or the global maximum, overestimates: [3, 0, 0, 2, 0, 4] has the answer 10, and a global maximum of 4 would claim a level of 4 above the 2.
    • Forgetting the bar itself. Because both maxima include bar i, height[i] can never exceed their minimum. Computing the maxima over the other bars only would allow negative terms, and the sum would be wrong.
    • Counting water on the end bars. No wall stands beyond the first or last bar, and the formula returns 0 for them automatically, since the maximum on the outer side is the bar itself.

    Testing

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