|
| 1 | +# 42. Trapping Rain Water |
| 2 | +# 🔴 Hard |
| 3 | +# |
1 | 4 | # https://leetcode.com/problems/trapping-rain-water/
|
2 |
| - |
| 5 | +# |
3 | 6 | # Tags: Array - Two Pointers - Dynamic Programming - Stack - Monotonic Stack
|
4 | 7 |
|
5 | 8 | import timeit
|
6 | 9 | from typing import List
|
7 | 10 |
|
| 11 | +# 1e4 calls |
| 12 | +# » TwoPointers 0.04256 seconds |
| 13 | +# » MonotonicStack 0.08529 seconds |
8 | 14 |
|
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. |
11 | 28 | #
|
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. |
14 | 32 | #
|
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: |
18 | 36 | def trap(self, height: List[int]) -> int:
|
| 37 | + # Initialize two pointers to the ends of the array. |
19 | 38 | 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. |
20 | 41 | trapped, max_left, max_right = 0, height[0], height[-1]
|
| 42 | + # Keep iterating while the pointers are not next to each other. |
21 | 43 | 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. |
22 | 47 | if max_left >= max_right:
|
| 48 | + # The left max is higher, update the right pointer. |
23 | 49 | 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. |
24 | 53 | if height[right_idx] > max_right:
|
25 | 54 | 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. |
26 | 59 | elif height[right_idx] < max_right:
|
27 | 60 | 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. |
28 | 64 | else:
|
| 65 | + # The right max is higher, update the left pointer. |
29 | 66 | 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. |
30 | 70 | if height[left_idx] > max_left:
|
31 | 71 | 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. |
32 | 76 | elif height[left_idx] < max_left:
|
33 | 77 | 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. |
35 | 82 | return trapped
|
36 | 83 |
|
37 | 84 |
|
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. |
39 | 97 | #
|
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. |
44 | 103 | trapped = 0
|
| 104 | + # The monotonic stack with the indices of the latest tall |
| 105 | + # heights seen, in non-increasing order. |
45 | 106 | stack = []
|
| 107 | + # Iterate over all the heights from left to right. |
46 | 108 | 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. |
47 | 113 | 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. |
49 | 121 | 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. |
52 | 140 | stack.append(i)
|
| 141 | + # Return the amount of trapped water. |
53 | 142 | return trapped
|
54 | 143 |
|
55 | 144 |
|
56 | 145 | def test():
|
57 |
| - executors = [LinearFromBothEnds, UsingStack] |
| 146 | + executors = [ |
| 147 | + TwoPointers, |
| 148 | + MonotonicStack, |
| 149 | + ] |
58 | 150 | tests = [
|
59 |
| - [[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6], |
60 | 151 | [[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], |
61 | 156 | ]
|
62 | 157 | for executor in executors:
|
63 | 158 | start = timeit.default_timer()
|
64 |
| - for _ in range(int(float("1"))): |
| 159 | + for _ in range(1): |
65 | 160 | for col, t in enumerate(tests):
|
66 | 161 | sol = executor()
|
67 | 162 | result = sol.trap(t[0])
|
68 | 163 | 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 | + ) |
72 | 168 | stop = timeit.default_timer()
|
73 | 169 | 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") |
75 | 172 | print(f"\033[92m» {res}\033[0m")
|
76 | 173 |
|
77 | 174 |
|
|
0 commit comments