Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions 0042.Trapping-Rain-Water/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 42. Trapping Rain Water

## step1

思考の流れ:

問題文の理解 -> 紙に書く -> 左右を入れ替えても同じことに気づく -> 水の高さが min(max_from_left[i], max_from_right[i]) であることに気づく -> 実装

全体で 24m ほど。問題文の理解にも時間がかかる問題だった。落ち着いて考察できたから解けたが、面接の状況では同じことができないかもしれない。

時間計算量 O(n), 空間計算量 O(n)

itertools.accumulateにreversedを適用してしまい、`TypeError: 'itertools.accumulate' object is not reversible`が発生した。

---

## step2

LLMに面接官役をやらせてみる

> Alright, that’s a solid start! Your logic is perfectly correct. You recognized that the water trapped at any given bar depends on the maximum height to its left and right, which shows you understand the core mechanics of the problem.
> Using itertools.accumulate is a clever way to build the prefix and suffix maximums quickly. However, looking at this implementation, I have a few concerns regarding its efficiency and how we are managing memory.
> Let's dig a bit deeper into this. I have a few questions for you.

1.
> Your current solution runs in $O(N)$ time, which is great. But what is the space complexity here?
> The Follow-up: "Can you optimize this to $O(1)$ auxiliary space? Imagine the input array has millions of elements; allocating all these extra lists will heavily tax the memory. Hint: Think about how we can use pointers to calculate this on the fly."

2.
> The Issue: You are reversing the list, accumulating it, converting it to a list, and then reversing it again via slicing [::-1].
> The Follow-up: "While it works, this involves multiple passes over the data just to set up the right-side maximums. It feels a bit clunky. Is there a way we can find the trapped water without needing to pre-compute and store the entire right-side history?"

3.
> The Issue: You calculated the differences first, stored them in lists, and then zipped them again. You could have just zipped max_from_left and max_from_right directly to find the bottleneck height.
> The Follow-up: "Mathematically, the water at index i is just min(max_left[i], max_right[i]) - height[i]. Why did we create two separate intermediate lists for the differences? Could we compress this logic?"

1, 3を受けて改善した。順方向のmaxはiteratorなのでメモリを消費しないが、逆方向はlistに変換するせいでO(n)のメモリを避けられない。

2でtwo pointerというキーワードが出た。解いているときに空間計算量 O(n) はかかってしまうだろうと考えて書いていたが、O(1)にできるらしい。

左と右のポインタのうち、小さいことが確定した時点でポインタを進められることに気がつきtwo pointerの解を得た。20mほどかかった。

過去に似た問題を解いた気がしたので、確認してみると以下の問題だった。

https://leetcode.com/problems/container-with-most-water/description/?envType=problem-list-v2&envId=rab78cw1

## 他の人のコード

https://github.com/shining-ai/leetcode/pull/63

スタックを使った解法は思いつかなかった。

```python
class Solution:
def trap(self, height: List[int]) -> int:
left_walls = []
total_water = 0
for right in range(len(height)):
while left_walls and height[left_walls[-1]] <= height[right]:
bottom_index = left_walls.pop()
if not left_walls:
break
left = left_walls[-1]
distance = right - left - 1
depth = min(height[left], height[right]) - height[bottom_index]
total_water += distance * depth
left_walls.append(right)
return total_water
```

> max_left ですと、最大の左を表しているように感じられます。 max_heights_in_left あたりはいかがでしょうか?

listであることがわかりやすいので、step1_revisedに反映する。max_heights_from_leftとした

https://github.com/Yuto729/leetcode/pull/69/changes

> Two Pointer 系の整合したコードの書き方

step2_two_pointerは (l, r) を未処理範囲としている。

step2_two_pointer2として [l, r]を未処理範囲としたものも書く。


## step3

最も馴染みの薄いstackを用いた解法を書く。
16 changes: 16 additions & 0 deletions 0042.Trapping-Rain-Water/step1_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import itertools


class Solution:
def trap(self, height: list[int]) -> int:
max_from_left = itertools.accumulate(height, max)
max_from_right = reversed(list(itertools.accumulate(reversed(height), max)))

difference_max_left = [m - h for m, h in zip(max_from_left, height)]
difference_max_right = [m - h for m, h in zip(max_from_right, height)]

trapped_water = 0
for left, right in zip(difference_max_left, difference_max_right):
trapped_water += min(left, right)

return trapped_water
16 changes: 16 additions & 0 deletions 0042.Trapping-Rain-Water/step1_list_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import itertools


class Solution:
def trap(self, height: list[int]) -> int:
max_heights_from_left = itertools.accumulate(height, max)
max_heights_from_right = reversed(
list(itertools.accumulate(reversed(height), max))
)

return sum(
min(m_left, m_right) - h
for m_left, m_right, h in zip(
max_heights_from_left, max_heights_from_right, height
)
)
20 changes: 20 additions & 0 deletions 0042.Trapping-Rain-Water/step2_two_pointer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def trap(self, height: list[int]) -> int:
index_left = 0
index_right = len(height) - 1

max_from_left = height[index_left]
max_from_right = height[index_right]

trapped_water = 0
while index_left < index_right:
if max_from_left <= max_from_right:
trapped_water += max_from_left - height[index_left]
index_left += 1
max_from_left = max(max_from_left, height[index_left])
else:
trapped_water += max_from_right - height[index_right]
index_right -= 1
max_from_right = max(max_from_right, height[index_right])

return trapped_water
20 changes: 20 additions & 0 deletions 0042.Trapping-Rain-Water/step2_two_pointer2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def trap(self, height: list[int]) -> int:
index_left = 0
index_right = len(height) - 1

max_from_left = 0
max_from_right = 0

trapped_water = 0
while index_left <= index_right:
if max_from_left <= max_from_right:
trapped_water += max(max_from_left - height[index_left], 0)
max_from_left = max(max_from_left, height[index_left])
index_left += 1
else:
trapped_water += max(max_from_right - height[index_right], 0)
max_from_right = max(max_from_right, height[index_right])
index_right -= 1

return trapped_water
20 changes: 20 additions & 0 deletions 0042.Trapping-Rain-Water/step3_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def trap(self, height: list[int]) -> int:
indices_of_decreasing_height = []
trapped_water = 0

for index_right, height_right in enumerate(height):
while (
indices_of_decreasing_height
and height[indices_of_decreasing_height[-1]] < height_right
):
bottom = indices_of_decreasing_height.pop()
if not indices_of_decreasing_height:
break
index_left = indices_of_decreasing_height[-1]
width = index_right - index_left - 1
bounded_height = min(height[index_left], height_right) - height[bottom]
trapped_water += width * bounded_height
indices_of_decreasing_height.append(index_right)

return trapped_water