From 9859be3cbdb6475e59f634711a57052dc9f613c6 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 17 Mar 2026 22:35:27 -0700 Subject: [PATCH 01/10] LeetCode 11: Add notes on first attempt --- leetcode/11/memo.md | 9 +++++++++ leetcode/11/step1_tle.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 leetcode/11/memo.md create mode 100644 leetcode/11/step1_tle.py diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md new file mode 100644 index 0000000..607a6d2 --- /dev/null +++ b/leetcode/11/memo.md @@ -0,0 +1,9 @@ +# Step 1 + +素直に全通り試すと 時間計算量は O(n^2)。 + +> 2 <= n <= 10^5 + +なので、C++ が 1秒間に10^9 回の処理が行えるとしても、大体 10 秒くらいのオーダーの処理時間がかかりそう。 + +しばらく考えてみたのだが、枝刈りは思いついても、時間計算量を O(nlogn) や O(n) にする方法が思いつかなかった。 diff --git a/leetcode/11/step1_tle.py b/leetcode/11/step1_tle.py new file mode 100644 index 0000000..7c115a6 --- /dev/null +++ b/leetcode/11/step1_tle.py @@ -0,0 +1,17 @@ +import functools + + +class Solution: + def maxArea(self, height: list[int]) -> int: + @functools.cache + def max_area_helper(first: int, last: int) -> int: + if last <= first: + return 0 + + return max( + (last - first) * min(height[first], height[last]), + max_area_helper(first + 1, last), + max_area_helper(first, last - 1), + ) + + return max_area_helper(0, len(height) - 1) From f4a95d75830aba46a8630fb5331f191d72d99fb2 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 11:37:48 -0700 Subject: [PATCH 02/10] LeetCode 11: Note LeetCode solution --- leetcode/11/memo.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index 607a6d2..9106d05 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -7,3 +7,27 @@ なので、C++ が 1秒間に10^9 回の処理が行えるとしても、大体 10 秒くらいのオーダーの処理時間がかかりそう。 しばらく考えてみたのだが、枝刈りは思いついても、時間計算量を O(nlogn) や O(n) にする方法が思いつかなかった。 + +LeetCode にポストされている Solution を見てみた。 + +```py +class Solution: + def maxArea(self, height: list[int]) -> int: + max_area = 0 + left = 0 + right = len(height) - 1 + + while left < right: + max_area = max(max_area, (right - left) * min(height[left], height[right])) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + + return max_area + +``` + +うーん?なんでTwo Pointersでうまくいくのだろう。少し考えてみる。 + +TODO: Check [https://github.com/thonda28/leetcode/pull/16](https://github.com/thonda28/leetcode/pull/16) From 31121962c88c77907b41a61bf4012efb48f88b68 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 11:49:58 -0700 Subject: [PATCH 03/10] LeetCode 11: Note thonda28-san's PR --- leetcode/11/memo.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index 9106d05..e9c55ca 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -8,6 +8,8 @@ しばらく考えてみたのだが、枝刈りは思いついても、時間計算量を O(nlogn) や O(n) にする方法が思いつかなかった。 +# Step 2 + LeetCode にポストされている Solution を見てみた。 ```py @@ -30,4 +32,6 @@ class Solution: うーん?なんでTwo Pointersでうまくいくのだろう。少し考えてみる。 -TODO: Check [https://github.com/thonda28/leetcode/pull/16](https://github.com/thonda28/leetcode/pull/16) +[thonda28 さんのPR](https://github.com/thonda28/leetcode/pull/16/changes#r1687386128) + +[Segment Tree](https://en.wikipedia.org/wiki/Segment_tree) を用いた解法もあるようだ。実装できるまで理解するのにかなり時間がかかりそうだから一旦置いておこうか。 From 2c571f8fdb563313a27e9ef43d027b4f70cf28b1 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 18:27:57 -0700 Subject: [PATCH 04/10] LeetCode 11: Rename file --- leetcode/11/{step1_tle.py => step1_memory_limit_exceeded.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename leetcode/11/{step1_tle.py => step1_memory_limit_exceeded.py} (100%) diff --git a/leetcode/11/step1_tle.py b/leetcode/11/step1_memory_limit_exceeded.py similarity index 100% rename from leetcode/11/step1_tle.py rename to leetcode/11/step1_memory_limit_exceeded.py From 3798a42fa8dac0d3e26c4ab1d027395928811447 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 18:46:39 -0700 Subject: [PATCH 05/10] LeetCode 11: Note my understanding on why two pointers work --- leetcode/11/memo.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index e9c55ca..2f15f25 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -32,6 +32,13 @@ class Solution: うーん?なんでTwo Pointersでうまくいくのだろう。少し考えてみる。 +わかった。 +`i`, `j` (`i < j`, `height[i] < height[j]`) の2点を考える。面積は、高さが `height[i]` で決まるので、`(j - i) * height[i]`。 +ここで、`j` をどれだけ減らしても、この面積を超えることはない。幅は小さくなる一方だし、`height[j]` が大きくなっても高さは `height[i]` で頭打ちだし、`height[j]` が小さくなれば高さも小さくなる。 +だから `height[i] < height[j]` のときは、`j` を減らして得られる面積は最初のものを超えないので、試す必要がない。 +`height[i] > height[j]` のときも同様。 +なので、Two Pointersで解ける。 + [thonda28 さんのPR](https://github.com/thonda28/leetcode/pull/16/changes#r1687386128) [Segment Tree](https://en.wikipedia.org/wiki/Segment_tree) を用いた解法もあるようだ。実装できるまで理解するのにかなり時間がかかりそうだから一旦置いておこうか。 From 6aca8f62b3a0ed50e40ebef0e7493e525f964773 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 20:05:03 -0700 Subject: [PATCH 06/10] LeetCode 11: Add notes on segment tree-based approach --- leetcode/11/memo.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index 2f15f25..c444a60 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -41,4 +41,11 @@ class Solution: [thonda28 さんのPR](https://github.com/thonda28/leetcode/pull/16/changes#r1687386128) -[Segment Tree](https://en.wikipedia.org/wiki/Segment_tree) を用いた解法もあるようだ。実装できるまで理解するのにかなり時間がかかりそうだから一旦置いておこうか。 +[Segment Tree](https://en.wikipedia.org/wiki/Segment_tree) を用いた解法もあるようだ。 +LeetCode の Solutions も眺めてみると、幾つか [Segment Tree を用いた解法](https://leetcode.com/problems/container-with-most-water/solutions/2998428/c-min-and-max-segment-tree-on-log10000-b-yh5c)が提示されている。 + +Segment Tree は区間内の最大値を返すことができて、今回の問題の場合、区間を高さ、値を `height` のインデックスに設定する。 +あるインデックス `i` に対して、その `height[i]` の値以上の高さを持つ一番右のインデックスが面積の最大を作るので、区間 `[height[i], 10^4]` という クエリの返すインデックスとの面積をチェックしていけばいい。ただしこれだけだと、右側が`height[i]`以上になるような場合しかチェックしていないので、[5,4,3,2,1] のような入力を扱うために、逆方向にも同様にクエリを行い、左側にある最も遠いインデックスも調べる必要がある。 +時間計算量は O(n log 10 ^ 4) = O(n) + +いくつか解法をみてみたが、今の私には荷が重そうなので、一旦スキップさせていただくことにする。 \ No newline at end of file From ddbe9c793fa0cadb78fa5669978f3850a4b22a3f Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 21:25:36 -0700 Subject: [PATCH 07/10] LeetCode 11: Add bucketed preprocessing solution --- leetcode/11/memo.md | 15 +++++++++- leetcode/11/step2_bucket_preprocessing.py | 34 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 leetcode/11/step2_bucket_preprocessing.py diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index c444a60..b72a919 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -10,6 +10,8 @@ # Step 2 +## Two Pointers + LeetCode にポストされている Solution を見てみた。 ```py @@ -39,6 +41,8 @@ class Solution: `height[i] > height[j]` のときも同様。 なので、Two Pointersで解ける。 +## Segment Tree + [thonda28 さんのPR](https://github.com/thonda28/leetcode/pull/16/changes#r1687386128) [Segment Tree](https://en.wikipedia.org/wiki/Segment_tree) を用いた解法もあるようだ。 @@ -48,4 +52,13 @@ Segment Tree は区間内の最大値を返すことができて、今回の問 あるインデックス `i` に対して、その `height[i]` の値以上の高さを持つ一番右のインデックスが面積の最大を作るので、区間 `[height[i], 10^4]` という クエリの返すインデックスとの面積をチェックしていけばいい。ただしこれだけだと、右側が`height[i]`以上になるような場合しかチェックしていないので、[5,4,3,2,1] のような入力を扱うために、逆方向にも同様にクエリを行い、左側にある最も遠いインデックスも調べる必要がある。 時間計算量は O(n log 10 ^ 4) = O(n) -いくつか解法をみてみたが、今の私には荷が重そうなので、一旦スキップさせていただくことにする。 \ No newline at end of file +いくつか解法をみてみたが、今の私には荷が重そうなので、一旦スキップさせていただくことにする。 + +## Suffix max/min preprocessing + +thonda28 さんのPR上のコメントを見て、あるインデックスに対してその高さ以上で一番右にあるインデックスさえわかれば良いなら、それを先に計算しておけば良いかも、と考えた。-> `step2_bucketed_preprocessing.py` + +時間計算量は、最大の高さをH、heightの長さをNとすると、O(H + N) (preprocessing + max area calculation) +空間計算量は、高さ分のbucket を持つので O(H)。 + +他にも、違う道のりで Two Pointers での解法に辿りつく話も thonda28-san のPR上でされていて、まだまだ隅々まで納得するのには時間がかかりそうだが、勉強になった。 \ No newline at end of file diff --git a/leetcode/11/step2_bucket_preprocessing.py b/leetcode/11/step2_bucket_preprocessing.py new file mode 100644 index 0000000..05aebaa --- /dev/null +++ b/leetcode/11/step2_bucket_preprocessing.py @@ -0,0 +1,34 @@ +import collections + + +class Solution: + def maxArea(self, height: list[int]) -> int: + max_height = max(height) + height_to_rightmost = [-1] * (max_height + 1) + height_to_leftmost = [float("inf")] * (max_height + 1) + for i, h in enumerate(height): + height_to_rightmost[h] = max(height_to_rightmost[h], i) + height_to_leftmost[h] = min(height_to_leftmost[h], i) + assert len(height_to_rightmost) == len(height_to_leftmost) + + min_height_to_rightmost = height_to_rightmost.copy() + min_height_to_leftmost = height_to_leftmost.copy() + for h in range(max_height - 1, -1, -1): + min_height_to_rightmost[h] = max( + min_height_to_rightmost[h], min_height_to_rightmost[h + 1] + ) + min_height_to_leftmost[h] = min( + min_height_to_leftmost[h], min_height_to_leftmost[h + 1] + ) + + max_area = 0 + for i, h in enumerate(height): + right = min_height_to_rightmost[h] + left = min_height_to_leftmost[h] + max_area = max( + max_area, + (right - i) * h, + (i - left) * h, + ) + + return max_area From 5f9c9dd0e5fa1e78638a48ca0ba3bd36cf966c03 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 21:28:50 -0700 Subject: [PATCH 08/10] LeetCode 11: Add step 2 solution --- leetcode/11/step2.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 leetcode/11/step2.py diff --git a/leetcode/11/step2.py b/leetcode/11/step2.py new file mode 100644 index 0000000..f5520bc --- /dev/null +++ b/leetcode/11/step2.py @@ -0,0 +1,14 @@ +class Solution: + def maxArea(self, height: list[int]) -> int: + left = 0 + right = len(height) - 1 + max_area = 0 + while left < right: + area = (right - left) * min(height[left], height[right]) + max_area = max(max_area, area) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + + return max_area From 43f9bae7697e5c21fd0b1d476b7383ee033e9847 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 21:35:50 -0700 Subject: [PATCH 09/10] LeetCode 11: Add step 3 solution --- leetcode/11/step3.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 leetcode/11/step3.py diff --git a/leetcode/11/step3.py b/leetcode/11/step3.py new file mode 100644 index 0000000..f5520bc --- /dev/null +++ b/leetcode/11/step3.py @@ -0,0 +1,14 @@ +class Solution: + def maxArea(self, height: list[int]) -> int: + left = 0 + right = len(height) - 1 + max_area = 0 + while left < right: + area = (right - left) * min(height[left], height[right]) + max_area = max(max_area, area) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + + return max_area From b58a8c649e2603cbe231fcb01afaa2804c4c1476 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 18 Mar 2026 21:37:38 -0700 Subject: [PATCH 10/10] LeetCode 11: Remove redundant blank line --- leetcode/11/memo.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/leetcode/11/memo.md b/leetcode/11/memo.md index b72a919..a046b3d 100644 --- a/leetcode/11/memo.md +++ b/leetcode/11/memo.md @@ -29,7 +29,6 @@ class Solution: right -= 1 return max_area - ``` うーん?なんでTwo Pointersでうまくいくのだろう。少し考えてみる。 @@ -61,4 +60,4 @@ thonda28 さんのPR上のコメントを見て、あるインデックスに対 時間計算量は、最大の高さをH、heightの長さをNとすると、O(H + N) (preprocessing + max area calculation) 空間計算量は、高さ分のbucket を持つので O(H)。 -他にも、違う道のりで Two Pointers での解法に辿りつく話も thonda28-san のPR上でされていて、まだまだ隅々まで納得するのには時間がかかりそうだが、勉強になった。 \ No newline at end of file +他にも、違う道のりで Two Pointers での解法に辿りつく話も thonda28-san のPR上でされていて、まだまだ隅々まで納得するのには時間がかかりそうだが、勉強になった。