Skip to content

Commit ebd91ed

Browse files
committed
LeetCode 42. Trapping Rain Water reformat
1 parent 2b40c7a commit ebd91ed

File tree

1 file changed

+121
-24
lines changed

1 file changed

+121
-24
lines changed

leetcode/trapping-rain-water.py

+121-24
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,174 @@
1+
# 42. Trapping Rain Water
2+
# 🔴 Hard
3+
#
14
# https://leetcode.com/problems/trapping-rain-water/
2-
5+
#
36
# Tags: Array - Two Pointers - Dynamic Programming - Stack - Monotonic Stack
47

58
import timeit
69
from typing import List
710

11+
# 1e4 calls
12+
# » TwoPointers 0.04256 seconds
13+
# » MonotonicStack 0.08529 seconds
814

9-
# Use a pointer and a max_height from each end and add the maximum water that can
10-
# be collected at each step.
15+
# If we get to the realization that the maximum height collected between
16+
# two values will be determined by the lower of the two heights,
17+
# independently of the value of the higher one, we can come up with a
18+
# greedy O(n) solution to this problem. We use both a pointer and a
19+
# max_height variable from each end. At each step, we update the pointer
20+
# that is on the side of the lower of the two max heights, that
21+
# guarantees that we know the amount of water that can be held at that
22+
# particular point. If the inner heights from that point are smaller,
23+
# then the two current max will determine the height of the water, if
24+
# we find a taller height, then, we still know that the max at the
25+
# current position is equal to the closer maximum because that one would
26+
# be one of the extremities of this "tank" and would mark the water
27+
# height independent of how much taller the other side is.
1128
#
12-
# Time complexity: O(n) - we visit each position of the list once.
13-
# Space complexity: O(1) - we use 5 variables independently of the size of the input.
29+
# Time complexity: O(n) - We visit each position of the list once.
30+
# Space complexity: O(1) - We use 5 variables independently of the size
31+
# of the input.
1432
#
15-
# Runtime: 147 ms, faster than 71.76% of Python3 online submissions for Trapping Rain Water.
16-
# Memory Usage: 15.9 MB, less than 77.48 % of Python3 online submissions for Trapping Rain Water.
17-
class LinearFromBothEnds:
33+
# Runtime: 120 ms, faster than 97.96%
34+
# Memory Usage: 16 MB, less than 81.24%
35+
class TwoPointers:
1836
def trap(self, height: List[int]) -> int:
37+
# Initialize two pointers to the ends of the array.
1938
left_idx, right_idx = 0, len(height) - 1
39+
# Initialize the current maximum height seen on both the left
40+
# and right hand side of the array.
2041
trapped, max_left, max_right = 0, height[0], height[-1]
42+
# Keep iterating while the pointers are not next to each other.
2143
while right_idx > left_idx + 1:
44+
# Move the pointer that is on the side of the lower max
45+
# height, if they are both the same, it does not matter
46+
# which pointer we update.
2247
if max_left >= max_right:
48+
# The left max is higher, update the right pointer.
2349
right_idx -= 1
50+
# If the new height is taller than the current max right
51+
# then we cannot collect any water at this point and
52+
# we want to update the max to be this new height.
2453
if height[right_idx] > max_right:
2554
max_right = height[right_idx]
55+
# Else, if the new height is lower than the current max
56+
# right, we know that we will be able to collect water
57+
# there, one unit wide and the difference between the
58+
# current height and the water level in height.
2659
elif height[right_idx] < max_right:
2760
trapped += max_right - height[right_idx]
61+
# We ignore the case when the max height and the current
62+
# height are the same, we cannot either collect water
63+
# or update the max value.
2864
else:
65+
# The right max is higher, update the left pointer.
2966
left_idx += 1
67+
# If the new height is taller than the current max left
68+
# then we cannot collect any water at this point and
69+
# we want to update the max to be this new height.
3070
if height[left_idx] > max_left:
3171
max_left = height[left_idx]
72+
# Else, if the new height is lower than the current max
73+
# left, we know that we will be able to collect water
74+
# there, one unit wide and the difference between the
75+
# current height and the water level in height.
3276
elif height[left_idx] < max_left:
3377
trapped += max_left - height[left_idx]
34-
78+
# We ignore the case when the max height and the current
79+
# height are the same, we cannot either collect water
80+
# or update the max value.
81+
# Return the amount of trapped water.
3582
return trapped
3683

3784

38-
# This should run slower, but in leetcode it seems to run a little faster
85+
# Use a monotonic stack to keep the index of heights that we have seen
86+
# from the latest max height on, iterate over all the heights starting
87+
# on the left, by popping any height smaller than the current from the
88+
# stack and calculating the amount of water trapped for that height,
89+
# we can move on with the calculations, if later we find an area
90+
# comprised between two higher heights, we add that "top" portion of
91+
# trapped water to the result.
92+
#
93+
# Time complexity: O(n) - We visit each height a maximum of two times,
94+
# on the main loop and later if we pop it from the stack.
95+
# Space complexity: O(n) - The stack may grow to the same size as the
96+
# input.
3997
#
40-
# Runtime: 133 ms, faster than 81.46% of Python3 online submissions for Trapping Rain Water.
41-
# Memory Usage: 15.8 MB, less than 77.48 % of Python3 online submissions for Trapping Rain Water.
42-
class UsingStack:
43-
def trap(self, height):
98+
# Runtime: 133 ms, faster than 81.46%
99+
# Memory Usage: 15.8 MB, less than 77.48%
100+
class MonotonicStack:
101+
def trap(self, height: List[int]) -> int:
102+
# Store the amount of trapped water.
44103
trapped = 0
104+
# The monotonic stack with the indices of the latest tall
105+
# heights seen, in non-increasing order.
45106
stack = []
107+
# Iterate over all the heights from left to right.
46108
for i, h in enumerate(height):
109+
# While the current height is taller than the last and
110+
# smallest height in the stack, pop it and check if any
111+
# water is being trapped between the previous height and the
112+
# current one.
47113
while stack and h > height[stack[-1]]:
48-
deal = stack.pop()
114+
# The last height in the stack is smaller than the
115+
# current one, pop it and use it as the "column" on top
116+
# of which the collected water sits.
117+
column = stack.pop()
118+
# If we still have any heights in the stack after the
119+
# last pop, we have a "column" check the amount of
120+
# water trapped on top of that column.
49121
if stack:
50-
w = i - stack[-1] - 1
51-
trapped += (min(h, height[stack[-1]]) - height[deal]) * w
122+
# The width of the trapped portion goes from the
123+
# index of the height that we are visiting to the
124+
# height that is trapping the water on the left,
125+
# i.e. left = 1, right = 3 => w = 3-1-1 => 1
126+
trapped_width = i - stack[-1] - 1
127+
# The height of the trapped area equals the
128+
# difference from the top of the "column" below the
129+
# trapped water to the point at which the water
130+
# would start to flow out, which is the smallest of
131+
# the left and right height. This greedy approach
132+
# works because if there was a higher height, we
133+
# would add that unprocessed area in a later step.
134+
trapped_height = min(h, height[stack[-1]]) - height[column]
135+
# The amount of trapped water on that section equals
136+
# the area with the computed width and height.
137+
trapped += trapped_height * trapped_width
138+
# Append this height to the stack, any smaller heights have
139+
# been popped already.
52140
stack.append(i)
141+
# Return the amount of trapped water.
53142
return trapped
54143

55144

56145
def test():
57-
executors = [LinearFromBothEnds, UsingStack]
146+
executors = [
147+
TwoPointers,
148+
MonotonicStack,
149+
]
58150
tests = [
59-
[[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6],
60151
[[4, 2, 0, 3, 2, 5], 9],
152+
[[1, 1, 1, 1, 1, 1], 0],
153+
[[6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1], 0],
154+
[[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6],
155+
[[6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 7], 40],
61156
]
62157
for executor in executors:
63158
start = timeit.default_timer()
64-
for _ in range(int(float("1"))):
159+
for _ in range(1):
65160
for col, t in enumerate(tests):
66161
sol = executor()
67162
result = sol.trap(t[0])
68163
exp = t[1]
69-
assert (
70-
result == exp
71-
), f"\033[93m» {result} <> {exp}\033[91m for test {col} using \033[1m{executor.__name__}"
164+
assert result == exp, (
165+
f"\033[93m» {result} <> {exp}\033[91m for"
166+
+ f" test {col} using \033[1m{executor.__name__}"
167+
)
72168
stop = timeit.default_timer()
73169
used = str(round(stop - start, 5))
74-
res = "{0:20}{1:10}{2:10}".format(executor.__name__, used, "seconds")
170+
cols = "{0:20}{1:10}{2:10}"
171+
res = cols.format(executor.__name__, used, "seconds")
75172
print(f"\033[92m» {res}\033[0m")
76173

77174

0 commit comments

Comments
 (0)