Trapping Rain Water

The problem description can be found in LeetCode.

Approach

An easy way to think about this problem is to first understand how much water a single location can contain.

For each position, the amount of water is bounded to the minimum value between the maximum height on the left and on the right, minus its height. In fact, think of a single cell of height 0 with their corresponding maximum left and right heights equal to n. That cell can contain n - h = n - 0 = n amount of water.

So, a naive implementation would consist of calculating the maximum left and right and then calculate the amount of trapped water by summing up all the water that can be contained in every cell:

from typing import List


class Solution:
    def trap(self, heights: List[int]) -> int:
        max_left = [0] * len(heights)
        max_right = [0] * len(heights)

        ml = 0
        for i in range(len(heights)):
            max_left[i] = ml
            ml = max(ml, heights[i])

        mr = 0
        for i in range(len(heights) - 1, -1, -1):
            max_right[i] = mr
            mr = max(mr, heights[i])

        ans = 0
        for i in range(len(heights)):
            trapped_water = min(max_left[i], max_right[i]) - heights[i]
            ans += max(trapped_water, 0)

        return ans

This would require for both time and space.

However, there is a better approach, which does not require this additional space. In fact, we only care about finding a higher height either on left or right for the single cell.

At every cell, if we increment the left pointer, it means that there is a higher height on the right. Alternatively, we could increment the right one if the left pointer points to a higher height value.

This means that we do not actually care about higher height which is either on the left or on the right. We just know that there is one, which means that the cell could contain some water.

Solution

from typing import List


class Solution:
    def trap(self, heights: List[int]) -> int:
        left, right = 0, len(heights) - 1
        left_max, right_max = heights[left], heights[right]
        ans = 0

        while left < right:
            if left_max < right_max:
                # there is a higher height on the `right`,
                # which means that the `left` pointer contains
                # a smaller value
                #
                # this would be an implicit `min(max_left[i], max_right[i])`
                # as we did in the previous implementation
                left += 1
                left_max = max(left_max, heights[left])
                ans += left_max - heights[left]
            else:
                # check the considerations above
                right -= 1
                right_max = max(right_max, heights[right])
                ans += right_max - heights[right]

        return ans

Complexity

  • Time:
  • Space:

Where is the size of the heights list.