Level Up Your Coding Skills & Crack Interviews — Save up to 50% or more on Educative.io Today! Claim Discount

Arrow
Table of contents

42. Trapping Rain Water

Trapping Rain Water, is one of the most famous Hard coding problems in the coding interview prep world. It is a favorite for top companies like Amazon, Google, and Meta because it tests how well you can turn a complex idea into a fast, efficient solution. Mastering this problem is a huge milestone for any developer because it teaches you exactly how to optimize your code from a slow approach to a high-performance one.

Problem statement

You’re given an array, height, of non-negative integers, where each element represents the height of a vertical bar (width = 1). After raining, water can be trapped between the bars. Return the total amount of water that can be trapped.

Constraints:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

Examples

1 / 6

Naive approach: Checking every bar individually

A simple way to solve Trapping Rain Water is to compute the trapped water at every index separately. For each position, you look for the tallest wall on its left and the tallest wall on its right. The water level above that position is limited by the shorter of those two walls. If that level is higher than the current bar, the difference is the water trapped at that position.

This approach is easy to understand, but it is inefficient because it repeatedly scans the array to find left and right maxima for every index.

Key idea and intuition

The crucial insight is that if you already know one side has a smaller boundary than the other, then that smaller boundary fully determines the water level on that side. The taller side cannot reduce the water level below the smaller boundary, so you can safely compute trapped water for positions on the smaller-boundary side without needing to know the exact shape of the interior on the other side.

Rainwater can only be trapped when there is a boundary on both sides of a position. The water height at any index is determined by the smaller of the best boundary on the left and the best boundary on the right. In other words, a position cannot hold water higher than the shorter wall that encloses it.

An optimized solution using Two Pointers

The optimized solution uses two pointers, one starting at the left end and the other at the right end, and moves them toward the center. While scanning, it tracks the highest bar seen so far from the left and from the right.

At each step, it moves the pointer on the side with the smaller current maximum boundary, because that side is the limiting wall for any water trapped there. If the current bar is lower than that side’s maximum boundary, the difference between the boundary height and the bar height is the water trapped at that position, and that amount is added to the running total. If the current bar is higher, the maximum boundary is updated.

The process continues until the two pointers meet. Each position is handled at most once, and no extra arrays are needed, so the solution runs in linear time and constant space.

Step-by-step algorithm

  1. Set two pointers: left at the start of the array (0), and right at the end of the array (n - 1).
  2. Initialize two running boundaries: left_max as the height at left, and right_max as the height at right
  3. Initialize total_water = 0 to store the final answer.
  4. While left < right, repeat the following:
    1. Compare left_max and right_max.
      1. If left_max < right_max:
        1. Move left one step to the right.
        2. Update left_max to be the maximum of the current left_max and height[left].
        3. The water trapped at this position is left_max - height[left]. Add it to total_water.
    2. Otherwise (i.e., right_max <= left_max):
      1. Move right one step to the left.
      2. Update right_max to be the maximum of the current right_max and height[right].
      3. The water trapped at this position is right_max - height[right]. Add it to total_water.
  5. When the pointers meet, return total_water.

Let’s look at the slides below to better understand the solution algorithm.

1 / 5

Python code for the optimized solution

Below is the complete Python implementation for the optimized solution, including the two-pointer logic we discussed above.

Time complexity

Each element in the input array is visited exactly once. Regardless of how the heights are distributed, the pointers will meet in the middle after exactly n steps, where n is the number of bars in the elevation map or length of height. Therefore, the overall time complexity is O(n).

Space complexity

The space complexity is O(1) as the algorithm only uses a constant number of variables and no extra data structures.

Common mistakes to avoid

Before you finalize your implementation for Trapping Rain Water, make sure to avoid these common errors:

  • The “Global Max” Fallacy: Assuming water level is determined by the absolute tallest peak; in reality, it is limited by the shorter of the two tallest walls flanking a gap.
  • Missing the Subtraction: Forgetting to subtract the bar’s own height from the water level (Total = Level - height[i]), which results in calculating the total volume rather than just the trapped water.
  • Neglecting Edge Cases: Failing to handle arrays with fewer than 3 elements (length 0, 1, or 2), which are physically incapable of forming a valley to trap water.
  • Incorrect Pointer Sequencing: Moving the left or right pointer before calculating the water for that specific index or before updating the running maximum.
  • Index vs. Height Confusion: Accidentally using the pointer variable (the index) in mathematical operations instead of the actual value stored at that index (height[left]).

Frequently Asked Questions

Why is the two-pointer approach preferred over the Dynamic Programming method?

While both approaches have a linear time complexity of O(n), the Dynamic Programming method requires O(n) extra space to store the pre-computed left and right maximum arrays. The two-pointer approach achieves the same result in O(1) constant space by calculating those boundaries on the fly.

How would you solve this using a Monotonic Stack?

You can maintain a stack of indices where heights are in decreasing order. When you encounter a bar taller than the stack’s top, it serves as a right boundary. The popped element becomes the “floor,” and the new stack top becomes the left boundary. The water is then calculated horizontally in layers rather than vertically.

If the width of each bar was variable instead of 1, how would the logic change?

The core logic remains the same. You would still calculate the height of the water at each index using min(left_max, right_max) – height[i]. However, instead of adding just that value, you would multiply it by the width of the bar at that specific index before adding it to the total.

What justifies moving only the smaller pointer?

The amount of water at any point is limited by the shorter of the two tallest walls. If left_max < right_max, we know for a fact that left_max is the limiting factor for the left pointer. Even if there are even taller bars further to the right, they won’t change the water level at the current left position.

How does the algorithm handle a strictly increasing or strictly decreasing elevation map?

In a strictly increasing or decreasing terrain, one of the max variables will always be equal to the current height being processed. As a result, the calculation max_height - current_height will always equal 0. The algorithm correctly returns a total volume of 0, as no valleys exist to trap water.

Share with others:

Leave a Reply

Your email address will not be published. Required fields are marked *