Files
codetutor/backend/data/questions/trapping-rain-water.yaml

202 lines
7.9 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
title: Trapping Rain Water
slug: trapping-rain-water
difficulty: hard
leetcode_id: 42
leetcode_url: https://leetcode.com/problems/trapping-rain-water/
categories:
- arrays
- two-pointers
- stack
patterns:
- slug: two-pointers
is_optimal: false
- slug: monotonic-stack
is_optimal: true
function_signature: "def trap(height: list[int]) -> int:"
test_cases:
visible:
- input: { height: [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] }
expected: 6
- input: { height: [4, 2, 0, 3, 2, 5] }
expected: 9
hidden:
- input: { height: [0, 0, 0] }
expected: 0
- input: { height: [3, 0, 3] }
expected: 3
- input: { height: [1, 2, 3, 4, 5] }
expected: 0
- input: { height: [5, 4, 1, 2] }
expected: 1
- input: { height: [2, 0, 2] }
expected: 2
- input: { height: [4, 2, 3] }
expected: 1
- input: { height: [5, 2, 1, 2, 1, 5] }
expected: 14
description: |
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.
constraints: |
- `n == height.length`
- `1 <= n <= 2 × 10^4`
- `0 <= height[i] <= 10^5`
examples:
- input: "height = [0,1,0,2,1,0,1,3,2,1,2,1]"
output: "6"
explanation: "The elevation map traps 6 units of water between the bars."
- input: "height = [4,2,0,3,2,5]"
output: "9"
explanation: "Water fills the valleys: 2 + 4 + 1 + 2 = 9 units."
explanation:
intuition: |
Visualise the elevation map as a cross-section of terrain. After rain, water fills the valleys but can't rise above the surrounding walls.
Think of it like this: at any position `i`, the water level is determined by the **shorter** of the two walls — the tallest bar to the left and the tallest bar to the right. Water can't rise higher than this "limiting wall" without spilling over.
For position `i`:
- `left_max` = maximum height to the left of i
- `right_max` = maximum height to the right of i
- Water level at i = `min(left_max, right_max)`
- Water trapped at i = `water_level - height[i]` (if positive)
The clever insight for the two-pointer approach: if we know `left_max < right_max`, the water at the left position is limited by `left_max` — we don't need to know the exact `right_max`, just that it's bigger. This lets us process from both ends simultaneously.
approach: |
We solve this using **Two Pointers**:
**Step 1: Initialise pointers and tracking variables**
- `left = 0`, `right = n - 1` (start at both ends)
- `left_max = 0`, `right_max = 0` (maximum heights seen so far)
- `water = 0` (total water trapped)
&nbsp;
**Step 2: Process from both ends**
- While `left < right`:
- If `height[left] < height[right]`:
- If `height[left] >= left_max`: update `left_max`
- Else: water trapped = `left_max - height[left]`, add to total
- Move `left` right
- Else:
- If `height[right] >= right_max`: update `right_max`
- Else: water trapped = `right_max - height[right]`, add to total
- Move `right` left
&nbsp;
**Step 3: Return total water**
- After pointers meet, all positions have been processed
&nbsp;
Why process the shorter side? If `height[left] < height[right]`, the water at `left` is bounded by `left_max` (the right side is guaranteed to be at least as tall as `height[right]`, which is bigger). We can safely compute water at `left` without knowing the exact `right_max`.
common_pitfalls:
- title: Only Considering One Side
description: |
Water level at any position depends on BOTH the left maximum and right maximum. If you only track one side, you'll compute incorrect water levels.
The two-pointer approach cleverly tracks both sides by processing the limiting side first.
wrong_approach: "Only tracking left_max"
correct_approach: "Track both left_max and right_max"
- title: Counting Bar Height as Water
description: |
Water trapped at position i is `max_height - height[i]`, not just `max_height`. The bar itself occupies space and can't hold water.
wrong_approach: "water += left_max"
correct_approach: "water += left_max - height[left]"
- title: Not Updating Max Before Computing Water
description: |
Update `left_max` or `right_max` **before** computing water. If the current bar is taller than the previous max, no water is trapped there (it's a new "wall").
The code should check: if current >= max, update max; else compute water.
wrong_approach: "Computing water, then updating max"
correct_approach: "if height >= max: max = height; else: water += max - height"
key_takeaways:
- "**Water level = min(left_max, right_max)**: The shorter wall determines the water level"
- "**Two pointers eliminate precomputation**: No need to precompute left_max and right_max arrays"
- "**Process the limiting side**: If left is shorter, it's bounded by left_max; process it and move inward"
- "**Multiple approaches exist**: DP (precompute arrays), monotonic stack, and two pointers all work"
time_complexity: "O(n). Single pass with two pointers, processing each position once."
space_complexity: "O(1). Only a few variables for pointers and maximum values."
solutions:
- approach_name: Two Pointers
is_optimal: true
code: |
def trap(height: list[int]) -> int:
if not height:
return 0
left, right = 0, len(height) - 1
left_max, right_max = 0, 0
water = 0
while left < right:
if height[left] < height[right]:
# Left side is the limiting factor
if height[left] >= left_max:
# New wall — update max, no water here
left_max = height[left]
else:
# Valley — water trapped up to left_max
water += left_max - height[left]
left += 1
else:
# Right side is the limiting factor
if height[right] >= right_max:
right_max = height[right]
else:
water += right_max - height[right]
right -= 1
return water
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(1) — Only constant extra space.
We process from both ends, always moving the pointer on the shorter side. If the current height exceeds the running max, it becomes the new max (a wall). Otherwise, water is trapped equal to the difference between max and current height. The key insight: processing the shorter side first guarantees correct water calculation.
- approach_name: Monotonic Stack
is_optimal: false
code: |
def trap(height: list[int]) -> int:
stack = [] # Stores indices of bars in decreasing height
water = 0
for i, h in enumerate(height):
# Pop shorter bars and calculate water in the valley
while stack and h > height[stack[-1]]:
bottom = stack.pop()
if not stack:
break # No left boundary
# Calculate water in this layer
width = i - stack[-1] - 1
bounded_height = min(h, height[stack[-1]]) - height[bottom]
water += width * bounded_height
stack.append(i)
return water
explanation: |
**Time Complexity:** O(n) — Each index pushed and popped at most once.
**Space Complexity:** O(n) — Stack can hold up to n indices.
The stack maintains bars in decreasing order. When we encounter a taller bar, we pop shorter bars and calculate water trapped in the "valley" between the current bar and the previous taller bar on the stack. Water is computed layer by layer, horizontally.