From 8a71477b2d458f26132c08b20559e77f7a467ddf Mon Sep 17 00:00:00 2001 From: Yota Maeda Date: Wed, 5 Aug 2026 23:57:26 +0900 Subject: [PATCH 1/3] step1 --- 300-Longest-Increasing-Subsequence/note.md | 117 ++++++++++++++++++++ 300-Longest-Increasing-Subsequence/step1.py | 11 ++ 2 files changed, 128 insertions(+) create mode 100644 300-Longest-Increasing-Subsequence/note.md create mode 100644 300-Longest-Increasing-Subsequence/step1.py diff --git a/300-Longest-Increasing-Subsequence/note.md b/300-Longest-Increasing-Subsequence/note.md new file mode 100644 index 0000000..98dc921 --- /dev/null +++ b/300-Longest-Increasing-Subsequence/note.md @@ -0,0 +1,117 @@ +# 300. Longest Increasing Subsequence + +https://leetcode.com/problems/longest-increasing-subsequence/ + +## step1(まず通す) + +最初、 + +- `subsequence = []` を用意する +- リストの要素を前から見ていき、今見ている要素が + - subsequence の最後の要素より大きいなら末尾につけ加える + - subsequence の末尾より小さくて、その一つ前(後ろから2番目) より大きいなら subsequence の末尾と今見ている値を入れ替える + +でできそう?と思ったが、`nums = [10,11,12, 1, 2, 3, 4]` で破綻する。(↑だと 3 になるが実際は 4) + +他で思いついたのは、再帰的なやり方で + +- リストが長さ1ならそのリストがLIS +- リストの長さが2以上のとき、 + - もし最後の要素 > 「残りの部分におけるLIS」の最後の要素 → 「」と最後の要素を結合したものがLIS + - そうでないなら、「残りの部分におけるLIS」がもとの配列自体のLIS + +ただこれも最初のものと同じで、成り立っていない。 + +```python +# 成り立っていない +class Solution: + def get_lis_len_and_end_num(self, nums: list[int]) -> tuple[int, int]: + if len(nums) == 1: + return 1, nums[0] + + end_num = nums.pop() + rest_lenght, rest_end = self.get_lis_len_and_end_num(nums) + if rest_end < end_num: + return rest_lenght + 1, end_num + else: + return rest_lenght, rest_end + + def lengthOfLIS(self, nums: list[int]) -> int: + length_of_lis, _ = self.get_lis_len_and_end_num(nums) + return length_of_lis +``` + +答えを見る。 + +> dp[i] を次のように定義します。 +> dp[i] = nums[i] を最後の要素とする増加部分列の最大長 +> nums[i] より前にある要素をすべて確認し、nums[j] < nums[i] なら、nums[j] で終わる部分列の後ろに nums[i] を追加できます。 +> したがって dp[i] = max(dp[i], dp[j] + 1) となります。 + +上で考えていた方法は、「残りの部分」を見る時に暫定的な LIS だけを保持して他の情報を捨てていたのがよくなさそう。今一番長い列が将来も有利とは限らない、ということを理解できていない考え方だった。 + +ChatGPTによる解説 + +``` +例の途中までを見ます。 + +[10, 11, 12, 1, 2, 3] + +この時点では、長さ3の増加部分列が複数あります。 + +[10, 11, 12] 長さ3、末尾12 +[1, 2, 3] 長さ3、末尾3 + +どちらも長さは同じですが、将来伸ばしやすいのは末尾が小さい [1, 2, 3] です。 + +次に 4 が来た場合、 + +[10, 11, 12] + 4 → 追加できない +[1, 2, 3] + 4 → [1, 2, 3, 4] + +となります。 + +前半の再帰コードは、最初に [10, 11, 12] を選んだ時点で、後から始まる [1]、[1, 2]、[1, 2, 3] という可能性を捨てています。 + +## 「最長のものだけ覚える」では情報が足りない + +コードが再帰から返している情報は、 + +(length_of_lis, そのLISの末尾) + +の1組だけです。 + +しかし本当に必要なのは、たとえば次のような複数の候補です。 + +長さ1なら、末尾をどこまで小さくできるか +長さ2なら、末尾をどこまで小さくできるか +長さ3なら、末尾をどこまで小さくできるか +... + +[10, 11, 12, 1] を処理した時点では、 + +長さ1の候補: [1] 末尾1 +長さ2の候補: [10, 11] 末尾11 +長さ3の候補: [10, 11, 12] 末尾12 + +という情報を残す必要があります。 + +長さ3の候補だけを見れば [10, 11, 12] が最長ですが、次に 2 が来たときは、長さ1の [1] が重要になります。 + +[1] + 2 → [1, 2] + +さらに 3, 4 が来ることで、最終的に長さ4になります。 +``` + +この解説を読むと、 + +> しかし本当に必要なのは、たとえば次のような複数の候補です。 +> 長さ1なら、末尾をどこまで小さくできるか +> 長さ2なら、末尾をどこまで小さくできるか +> 長さ3なら、末尾をどこまで小さくできるか + +といいながら、末尾ごとに最大長さを持っている = 長さの重複があって情報が冗長、という感じがする。 + +## step2(整形&他の人のコードを読む) + +## step3(10分以内にさっとかける \* 3回) diff --git a/300-Longest-Increasing-Subsequence/step1.py b/300-Longest-Increasing-Subsequence/step1.py new file mode 100644 index 0000000..b19cc9f --- /dev/null +++ b/300-Longest-Increasing-Subsequence/step1.py @@ -0,0 +1,11 @@ +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + length_from_begin = [1 for i in range(len(nums))] + for i in range(len(nums)): + for j in range(i): + if nums[i] > nums[j]: + length_from_begin[i] = max( + length_from_begin[i], length_from_begin[j] + 1 + ) + + return max(length_from_begin) From a94feba075f31b51b00c7cced33628225276b92d Mon Sep 17 00:00:00 2001 From: Yota Maeda Date: Thu, 6 Aug 2026 00:36:42 +0900 Subject: [PATCH 2/3] step2 --- 300-Longest-Increasing-Subsequence/note.md | 33 +++++++++++++++++-- .../step2-bin.py | 14 ++++++++ 300-Longest-Increasing-Subsequence/step2.py | 16 +++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 300-Longest-Increasing-Subsequence/step2-bin.py create mode 100644 300-Longest-Increasing-Subsequence/step2.py diff --git a/300-Longest-Increasing-Subsequence/note.md b/300-Longest-Increasing-Subsequence/note.md index 98dc921..e90539e 100644 --- a/300-Longest-Increasing-Subsequence/note.md +++ b/300-Longest-Increasing-Subsequence/note.md @@ -103,7 +103,21 @@ ChatGPTによる解説 さらに 3, 4 が来ることで、最終的に長さ4になります。 ``` -この解説を読むと、 +## step2(整形&他の人のコードを読む) + +> うーん、読みにくいと思ったんですが、理由が +> lengths +> の意味がパズルになっているからだと思いました。 +> +> 日本語で長々と書くと、「lengths[i] とは、仮に nums[i] がシーケンスの最後であると仮定した場合に可能な、最も長いシーケンスの長さ」ですよね。 +> +> まあ、「長さ(複数)」であることには間違いないですが、「長さ」とだけいわれて、ああ「仮に nums[i] がシーケンスの最後であると仮定した場合に可能な、最も長いシーケンスの長さ」ってことね、とならず、それを推測するパズルになっています。 +> +> これを前提とすると、内側のループは、関数にすることができるはずで、「i よりも左で、nums[i] 未満で終わる最大のシーケンスの長さ」を返させればいいですね。 + +https://discord.com/channels/1084280443945353267/1200089668901937312/1209563502407065602 + +step1 の解説を読むと、 > しかし本当に必要なのは、たとえば次のような複数の候補です。 > 長さ1なら、末尾をどこまで小さくできるか @@ -112,6 +126,21 @@ ChatGPTによる解説 といいながら、末尾ごとに最大長さを持っている = 長さの重複があって情報が冗長、という感じがする。 -## step2(整形&他の人のコードを読む) +素直に、「長さごとの最小末尾要素」を持つようにするとどうなるか。 +→ + +> 2番の解法は、上のリンクで書いた私の考えに近いものです。 +> +> 2番というのは、 +> 頭から舐めていって k 番目まで見たときに、 +> 「nums[k] までを使って、すべての長さ1のシーケンスを考えたときに、そのシーケンスの一番お尻の最小値」 +> 「nums[k] までを使って、すべての長さ2のシーケンスを考えたときに、そのシーケンスの一番お尻の最小値」 +> 「nums[k] までを使って、すべての長さ3のシーケンスを考えたときに、そのシーケンスの一番お尻の最小値」 +> …… +> というものです。これは昇順に並んでいるはずで、nums[k + 1] を使って、これを更新することは可能ですね。 +> +> これは、sorted array なので二分探索で、どこを更新するかは絞れます。 + +https://discord.com/channels/1084280443945353267/1200089668901937312/1209563502407065602 ## step3(10分以内にさっとかける \* 3回) diff --git a/300-Longest-Increasing-Subsequence/step2-bin.py b/300-Longest-Increasing-Subsequence/step2-bin.py new file mode 100644 index 0000000..03bd1b4 --- /dev/null +++ b/300-Longest-Increasing-Subsequence/step2-bin.py @@ -0,0 +1,14 @@ +from bisect import bisect_left + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + smallest_end_for_lis_lentgh: list[int] = [] + for i in range(len(nums)): + length_to_update = bisect_left(smallest_end_for_lis_lentgh, nums[i]) + if length_to_update == len(smallest_end_for_lis_lentgh): + smallest_end_for_lis_lentgh.append(nums[i]) + else: + smallest_end_for_lis_lentgh[length_to_update] = nums[i] + + return len(smallest_end_for_lis_lentgh) diff --git a/300-Longest-Increasing-Subsequence/step2.py b/300-Longest-Increasing-Subsequence/step2.py new file mode 100644 index 0000000..4bdbe33 --- /dev/null +++ b/300-Longest-Increasing-Subsequence/step2.py @@ -0,0 +1,16 @@ +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + lis_length_ending_at = [1 for i in range(len(nums))] + + def max_extendable_lis_length_before(index: int) -> int: + max_length = 0 + for i in range(index): + if nums[i] < nums[index]: + max_length = max(max_length, lis_length_ending_at[i]) + + return max_length + + for i in range(len(nums)): + lis_length_ending_at[i] = max_extendable_lis_length_before(i) + 1 + + return max(lis_length_ending_at) From f1bb80af438a212b6f417a3178a6834af332d81e Mon Sep 17 00:00:00 2001 From: Yota Maeda Date: Thu, 6 Aug 2026 00:39:42 +0900 Subject: [PATCH 3/3] step3 --- 300-Longest-Increasing-Subsequence/step3.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 300-Longest-Increasing-Subsequence/step3.py diff --git a/300-Longest-Increasing-Subsequence/step3.py b/300-Longest-Increasing-Subsequence/step3.py new file mode 100644 index 0000000..21d4352 --- /dev/null +++ b/300-Longest-Increasing-Subsequence/step3.py @@ -0,0 +1,14 @@ +from bisect import bisect_left + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + smallest_end_for_lengths = [] + for i in range(len(nums)): + length_to_update = bisect_left(smallest_end_for_lengths, nums[i]) + if length_to_update == len(smallest_end_for_lengths): + smallest_end_for_lengths.append(nums[i]) + else: + smallest_end_for_lengths[length_to_update] = nums[i] + + return len(smallest_end_for_lengths)