From d9dccdc98c70a8b4564672f1abad996731f9b68f Mon Sep 17 00:00:00 2001 From: tom4649 Date: Tue, 31 Mar 2026 06:16:52 +0900 Subject: [PATCH 01/40] 35. Search Insert Position --- 35/memo.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 35/sol1.py | 6 ++++ 35/sol2.py | 21 ++++++++++++ 35/sol3.py | 11 +++++++ 35/sol4.py | 9 +++++ 5 files changed, 144 insertions(+) create mode 100644 35/memo.md create mode 100644 35/sol1.py create mode 100644 35/sol2.py create mode 100644 35/sol3.py create mode 100644 35/sol4.py diff --git a/35/memo.md b/35/memo.md new file mode 100644 index 0000000..02a7334 --- /dev/null +++ b/35/memo.md @@ -0,0 +1,97 @@ +# 35. Search Insert Position + +sol1.py: bisect ライブラリを使うと秒殺 + +これでは何の練習にならないので自力で書く + +sol2.py:インデックスの1のずれで混乱 + +sol3.pyに改善。bisect_leftに関数を分けるのもやめる + + +left == rightのときこれらが同時になりたつ→答え +leftに+1があるので無限ループが発生しない + +https://discord.com/channels/1084280443945353267/1196498607977799853/1269532028819476562 + +> 二分探索を、 [false, false, false, ..., false, true, true, ture, ..., true] と並んだ配列があったとき、 false と true の境界の位置を求める問題、または一番左の true の位置を求める問題と捉えているか? +位置を求めるにあたり、答えが含まれる範囲を狭めていく問題と捉えているか? +範囲を考えるにあたり、閉区間・開区間・半開区間の違いを理解できているか? +用いた区間の種類に対し、適切な初期値を、理由を理解したうえで、設定できるか? +用いた区間の種類に対し、適切なループ不変条件を、理由を理解したうえで、設定できるか? +用いた区間の種類に対し、範囲を狭めるためのロジックを、理由を理解したうえで、適切に記述できるか? + + +```python +# 1. [false, false, false, ..., false, true, true, ture, ..., true] と並んだ配列があったとき、 +# false と true の境界の位置を求める問題と捉える。 +# ここでは、 target より小さい値は false、 target 以上の値は true とする。 +# 2. 位置を求めるにあたり、答えが含まれる範囲を狭めていく問題と捉える。 +# 3. 範囲を考えるにあたり、半開区間を用いる。 +# 4. 初期値は 0, len(numbers) とする。 +def lower_bound(numbers, left, right, target): + # 5. ループの終了状態で false, false, ..., false, [true), true, ..., true となって欲しい。 + # この書き方は正式な書き方ではなく、自分が考えるときのイメージです。 + # [ は区間の左端で、 true の左側にあります。 ) は区間の右端で、 true の右側にあります。 + # ただし、右側は開区間のため、インデックスの値は true の位置と同じで、 true を区間には含めない。 + # よってループの不変条件は left < right とする。 + while left < right: + middle = (left + right) // 2 + # target より小さい値は false、 target 以上の値は true とする。 + if (numbers[middle] < target): + # 6. 右に狭める場合。 + # middle の位置の要素を、狭めたあとの区間に含めたくない。 + # 左側は閉区間である。 + # middle の位置の要素を区間に含めないようにするには、 + # left の位置を middle + 1 にすればよい。 + left = middle + 1 + else: + # 左に狭める場合。 + # middle の位置の要素を、狭めたあとの区間に含めたくない。 + # なぜならば、今回は境界の位置を求める問題と捉ええているためである。 + # 別の言い方をすると、境界の位置を middle より左側にしたい。 + # 右側は開区間である。 + # middle の位置の要素を区間に含めないようにするには、 + # right の位置を middle にすればよい。 + right = middle + return left + + +def upper_bound(numbers, left, right, target): + while left < right: + middle = (left + right) // 2 + if (numbers[middle] <= target): + left = middle + 1 + else: + right = middle + return left + + +if __name__ == '__main__': + print(lower_bound([0, 1, 2, 3, 4, 5], 0, 6, 3)) + print(upper_bound([0, 1, 2, 3, 4, 5], 0, 6, 3)) +``` + + +保証している条件 +nums[left-1] < target +nums[right] >= target or rightが右端 + +ループ不変条件: left < right + +つまり flag_nums = [n>=target for n in nums] としたとき +i < left→ flag_nums[i] == False +i >= right → flag_nums[i] == True +終了時 left = rightでは right: flag_nums[i] == Trueを満たす最小のi が保証される + +https://github.com/seal-azarashi/leetcode/pull/38 + +しっかりと理解する必要があるな + +> 二分探索で「書き方」を固定することはできるのですが、「読む方法」を固定することは普通はできないです。書いている人がどういう考えで書くか普通は強制できないからです。 +> ジャッジシステムを相手にしているならば別にいいのですが、人間に技術面接で出題された場合は、多くの場合、どうしてそのコードが動くのかを聞きます。 +> 技術面接で見ているのが「一緒に働いたときに成果がより出るか」で、そのために必要な「簡単なコードが読めて書けて管理ができる」かを知りたいからです。(余計な絶対値があれば、分かっているかより気になるでしょう。) +> というわけで、書き方を固定してもいいけれども、幅のある表現を読めるようにしてくださいね、ということです。 + +https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0 + diff --git a/35/sol1.py b/35/sol1.py new file mode 100644 index 0000000..1a82352 --- /dev/null +++ b/35/sol1.py @@ -0,0 +1,6 @@ +import bisect + + +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + return bisect.bisect_left(nums, target) diff --git a/35/sol2.py b/35/sol2.py new file mode 100644 index 0000000..d549fc1 --- /dev/null +++ b/35/sol2.py @@ -0,0 +1,21 @@ +def bisect_left(nums, target): + left = 0 + right = len(nums) - 1 + while right - left > 1: + mid = left + (right - left) // 2 + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + else: + right = mid + if target <= nums[left]: + return left + if target <= nums[right]: + return right + return right + 1 + + +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + return bisect_left(nums, target) diff --git a/35/sol3.py b/35/sol3.py new file mode 100644 index 0000000..2789436 --- /dev/null +++ b/35/sol3.py @@ -0,0 +1,11 @@ +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) + while right > left: + mid = left + (right - left) // 2 + if nums[mid] < target: + left = mid + 1 + else: + right = mid + return left diff --git a/35/sol4.py b/35/sol4.py new file mode 100644 index 0000000..cb2a394 --- /dev/null +++ b/35/sol4.py @@ -0,0 +1,9 @@ +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + if not nums: + return 0 + right = len(nums) + mid = right // 2 + if nums[mid] < target: + return mid + 1 + self.searchInsert(nums[mid + 1 : right], target) + return self.searchInsert(nums[:mid], target) From a5ba63382f4880b5dc0b08760f716c23d3e05e15 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Tue, 31 Mar 2026 16:54:56 +0900 Subject: [PATCH 02/40] Add suggested changes --- 35/memo.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/35/memo.md b/35/memo.md index 02a7334..e951ad2 100644 --- a/35/memo.md +++ b/35/memo.md @@ -84,6 +84,35 @@ i < left→ flag_nums[i] == False i >= right → flag_nums[i] == True 終了時 left = rightでは right: flag_nums[i] == Trueを満たす最小のi が保証される +--- +## コメントをいただいて追記: + +### ループ不変式: + +i < left -> nums[i] <= target (1) + +&& j >= right -> nums[j] > target (2) + + +### 成り立つことの確認 + +#### 初期化時: left=0, right=len(nums) +i < left, j >= rightとなるi, jは存在しない。 + +#### 維持: ある反復で成り立つと仮定して次の反復で成り立つことを示す。場合分け。 +- nums[middle] <= target の場合: numsがソートされているため left=middle+1とした場合、(1) は依然として成立 +- nums[middle] > target の場合 numsがソートされているため right=middle とした場合(2) は依然として成立 + +#### 終了: left < rightがFalseとなる。更新式よりleft==rightである +(1),(2)と合わせて、 + +i nums[i] <= target && i >= left -> nums[i] > target + +これはleftがnums[i] > targetを満たす最小のiであることを意味する。 + + +--- + https://github.com/seal-azarashi/leetcode/pull/38 しっかりと理解する必要があるな From f90cfb3d1a74e2a19a75e39f893d6e3e28351133 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Wed, 29 Apr 2026 10:29:35 +0900 Subject: [PATCH 03/40] [0141] Linked List Cycle --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d63cee4..858d0da 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ # Coding + +
+[0141] Linked List Cycle + +- `set`を使った解法 +- `set`と`dict`がどちらもhashtableで値を格納している +- そのためdictのkeyはhashableである必要がある +- `__eq__` が `True` なら、`__hash__` も等しくなければならない + +
From 719face295442cc34cade3acd0e20c766b994101 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Wed, 29 Apr 2026 16:51:42 +0900 Subject: [PATCH 04/40] 142. Linked List Cycle II --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 858d0da..92c8937 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,12 @@ - `__eq__` が `True` なら、`__hash__` も等しくなければならない +
+[0142] Linked List Cycle II + +- Floydの循環検出法(有名だが常識ではない) +- `is`と`==`の違い: オブジェクトの同一性を比較する。同じインスタンスかどうか + - `is`: オブジェクトの同一性を比較する。同じインスタンスかどうかを`id()`で判定(CPythonではメモリアドレスとして実装されている)。 + - `==`: `__eq__`でオブジェクトの等価性を比較する。 + +
From 50184726036012d09eab002f2a3c8907a3d3591f Mon Sep 17 00:00:00 2001 From: tom4649 Date: Wed, 29 Apr 2026 22:20:57 +0900 Subject: [PATCH 05/40] [83] Remove Duplicates from Sorted List --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 92c8937..0609ef7 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,9 @@ - `==`: `__eq__`でオブジェクトの等価性を比較する。 +[83] Remove Duplicates from Sorted List + +- Linked Listの操作 +- 変数名には組み込み名と衝突する名前を使わない + + From e6a26fc700d91fe0cc14b8f837ce356cf751681c Mon Sep 17 00:00:00 2001 From: tom4649 Date: Wed, 29 Apr 2026 22:52:20 +0900 Subject: [PATCH 06/40] 82. Remove Duplicates from Sorted List II --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0609ef7..c13698e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Coding
-[0141] Linked List Cycle +0141.Linked List Cycle - `set`を使った解法 - `set`と`dict`がどちらもhashtableで値を格納している @@ -10,7 +10,7 @@
-[0142] Linked List Cycle II +0142.Linked List Cycle II - Floydの循環検出法(有名だが常識ではない) - `is`と`==`の違い: オブジェクトの同一性を比較する。同じインスタンスかどうか @@ -18,9 +18,17 @@ - `==`: `__eq__`でオブジェクトの等価性を比較する。
-[83] Remove Duplicates from Sorted List +83.Remove Duplicates from Sorted List - Linked Listの操作 - 変数名には組み込み名と衝突する名前を使わない + +82.Remove Duplicates from Sorted List II + +- 変数名の意味を考える +- currentの名前は情報量がない +- dummyを番兵として用いる方法は自然に解釈できる + + From f8506e1a22df6e5a3509aab91039930861153270 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 06:09:14 +0900 Subject: [PATCH 07/40] 2. Add Two Numbers --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index c13698e..4252845 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ +
82.Remove Duplicates from Sorted List II - 変数名の意味を考える @@ -32,3 +33,12 @@ - dummyを番兵として用いる方法は自然に解釈できる
+ + +
+0002.Add Two Numbers + +- 多倍長整数の加算 + +
+ From 30c303ba5313a5e854a5dd4892e454b9e906805d Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 08:44:23 +0900 Subject: [PATCH 08/40] 20.Valid-Parentheses --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 4252845..1c61701 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,12 @@ +
+20. Valid Parentheses + +- PEP-8 と Google Style Guide では strings, lists, tuples は implicit で真偽判定 +- 副作用のある式を条件のところに書かない方が良いかもしれない。読む側が頭の中で実行順序を追う必要が出るため。e.g. `stack.pop()` + +
+ + From 24a843839c97432d46ebfea5a7365e5eb20b20fa Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 09:05:26 +0900 Subject: [PATCH 09/40] 206.Reverse-Linked-List --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1c61701..d9c2e34 100644 --- a/README.md +++ b/README.md @@ -50,4 +50,12 @@ +
+206. Reverse Linked List + +- 一度の操作で行うポインタの付け替え + +
+ + From 7528c40bb2301eb2d7bd59844f9e089432c31291 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 11:00:44 +0900 Subject: [PATCH 10/40] 703. Kth Largest Element in a Stream --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9c2e34..5942765 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ - `==`: `__eq__`でオブジェクトの等価性を比較する。 + +
83.Remove Duplicates from Sorted List - Linked Listの操作 @@ -53,9 +55,16 @@
206. Reverse Linked List -- 一度の操作で行うポインタの付け替え +- 一度の走査で行うポインタの付け替え
+
+703. Kth Largest Element in a Stream +- heapqライブラリの使い方 +- マージソート、クイックソート、クイックセレクトの確認 +- ヒープの構築がO(n)であることが抜けていた + +
From f79e25f192c2146650c11686b7b86ee82f6db8a9 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 17:19:00 +0900 Subject: [PATCH 11/40] 347. Top K Frequent Elements --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 5942765..3345f3d 100644 --- a/README.md +++ b/README.md @@ -66,5 +66,13 @@ - ヒープの構築がO(n)であることが抜けていた
+
+347. Top K Frequent Elements + +- ヒープの構築はO(n) (再) +- クイックソートの実装 +- 一行で行う操作を増やしすぎない + +
From 74fb5b6c1d8eb92cdf1dfb3470f2de548f451e55 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Thu, 30 Apr 2026 20:10:35 +0900 Subject: [PATCH 12/40] 98. Validate Binary Search Tree --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 3345f3d..97315bc 100644 --- a/README.md +++ b/README.md @@ -75,4 +75,13 @@ +
+98. Validate Binary Search Tree + +- 木の探索順(preorder, inorder, postorder) +- 再帰とループの変換 +- ロジックの流暢さ + +
+ From 9460a312e184c0600ca1ffc098b315e8b367446a Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 06:14:04 +0900 Subject: [PATCH 13/40] 373.Find K Pairs with Smallest Sums --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 97315bc..bc77d1a 100644 --- a/README.md +++ b/README.md @@ -84,4 +84,12 @@ +
+373.Find K Pairs with Smallest Sums + +- 全組み合わせを作らず、次に小さくなりうる候補だけをヒープで管理する +- 分からない場合には手作業で考える + +
+ From 6b708ca5716e29540ed264b104c1d98849c3fd75 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 10:06:40 +0900 Subject: [PATCH 14/40] 1. Two Sum --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bc77d1a..0b4d364 100644 --- a/README.md +++ b/README.md @@ -92,4 +92,14 @@ +
+1. Two Sum + +- Hashmapを使うと、必要な相手の値を平均O(1)で確認できるため、全探索のO(n^2)をO(n)に改善できる +- Type HintsはPython3から導入され、Docstringの型指定が使われなくなった +- 実装から時間計算量を見積もる時の目安 + - C++: 約 1~10 億 (10^8~10^9) ステップ/秒、Python: 約 100 万~1000 万 (10^6~10^7) ステップ/秒 + +
+ From 49ab830a34c45d393db2da634a3ffb14cef55232 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 10:32:43 +0900 Subject: [PATCH 15/40] 49. Group Anagrams --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 0b4d364..c9d0997 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,12 @@ +
+49. Group Anagrams + +- 理論上は文字頻度を使う方が速いが、Pythonの組み込みsortはC実装で高速なので、入力サイズによってはsortedを使う方が実測で速いことがある +- listはmutableでhashableではないため、dictのkeyにするにはtupleへ変換する必要がある + +
+ From f750e185d0ecde6c52a9878a7b282fd6cccbc078 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 13:26:57 +0900 Subject: [PATCH 16/40] 347. Top K Frequent Elements --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index c9d0997..f8d743c 100644 --- a/README.md +++ b/README.md @@ -110,4 +110,18 @@ +
+349. Intersection of Two Arrays + +- setはhash tableで実装されており、構築は平均O(n)、membership checkは平均O(1) +- 短い方をsetにすると、空間計算量をO(min(l1, l2))に抑えられる +- 入力がソート済み、片方だけ巨大、メモリ制約が厳しいなど、追加条件によって適した解法は変わる +- ソート済み配列同士なら、マージソートのmergeに近いtwo pointersでO(l1 + l2) +- マージソート + - pros: 最悪計算量もO(n log n)で安定、stable sort、外部ソートに向く + - cons: 配列では追加メモリO(n)、コピーの定数倍が重い、実測ではquick sortより遅いことが多い + + +
+ From 4448e090edc439af54ba5ec0770a912e2276e179 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 16:00:28 +0900 Subject: [PATCH 17/40] 929. Unique Email Addresses --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index f8d743c..426f49f 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,13 @@ +
+929. Unique Email Addresses + +- `str.partition`: 区切り文字で「左、 +区切り文字、右」の3要素に分けるため、最初の1回だけ分割したい意図を表しやすい +- 正規表現、有限ステートマシン、文字列操作の複数方針 +- Pythonの文字列はimmutableなので、ループ内での文字列連結は避け、必要ならlistにappendして最後に`"".join()`する + +
+ From ecb06012ab57898ffa31289d59d39c6daf4f470b Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 16:22:51 +0900 Subject: [PATCH 18/40] 387. First Unique Character in a String --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 426f49f..97f683a 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,15 @@ +
+387. First Unique Character in a String + +- 頻度を先に数えてから再走査する解法はシンプルで堅いが、文字列を2回見る +- 文字が1回しか流れてこない設定では、現在ユニークな文字だけを順序付きで管理する発想が使える +- `OrderedDict`は挿入順を保てるため、重複した文字を削除していけば先頭が最初のユニーク文字になる +- `queue.Queue`はスレッド同期用の機能を持つため、単一スレッドのアルゴリズム用途では`collections.deque`の方が自然 +- `queue.Queue`には`peek`がないので、先頭を見ながら取り除く用途では`deque`の`q[0]`と`popleft()`が使いやすい +- `OrderedDict`の順序管理は、dictに加えてdoubly-linked listを持つことで実現できる +- doubly-linked listでは前後の参照を更新することで、ノード削除をO(1)で行える + +
From 0d08de040b03ad8c07c91b240080de9150cab5cb Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 20:00:08 +0900 Subject: [PATCH 19/40] 560. Subarray Sum Equals K --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 97f683a..601e16c 100644 --- a/README.md +++ b/README.md @@ -147,3 +147,14 @@ - doubly-linked listでは前後の参照を更新することで、ノード削除をO(1)で行える + +
+560. Subarray Sum Equals K + +- prefix sumの出現回数をhashmapで管理すると、全区間を列挙するO(n^2)ではなくO(n)で数えられる +- 累積和配列を作って後から処理するより、走査しながら過去のprefix sumを数える方がシンプルに書ける +- 和がKになる区間を列挙する問題に変えると、prefix sum同士の対応関係を理解しやすい +- `defaultdict(int)`を使うと、存在しないprefix sumの出現回数を0として扱えて実装が素直になる +- パフォーマンス不足はデータ量が増えたときに実行時間やメモリ使用量として現れるため、O(n^2)からO(n)への改善は実務でも効く + +
From a5ac3520b1ec7b46fd12e5eda3ccbaaf97b61c9b Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 1 May 2026 22:23:45 +0900 Subject: [PATCH 20/40] 200. Number of Islands --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 601e16c..d32ce55 100644 --- a/README.md +++ b/README.md @@ -158,3 +158,20 @@ - パフォーマンス不足はデータ量が増えたときに実行時間やメモリ使用量として現れるため、O(n^2)からO(n)への改善は実務でも効く +
+200. Number of Islands + +- DFSでは再帰でも書けるが、探索が深い場合はコールスタックの上限に注意が必要 +- 明示的なstackを使うと再帰を避けられ、深い探索でも扱いやすい +- 訪問済みの印は、gridを書き換える方法と`seen`集合で管理する方法がある +- gridを書き換える場合は、帰りがけではなく行きがけで印をつけると無限ループを防ぎやすい +- gridを書き換えると破壊的になってしまう +- 境界判定や隣接マスの列挙は関数に分けると、グリッド探索の本体が読みやすくなる +- 添字の変数名は`i`, `j`よりも`row`, `col`にすると、行と列の意味が明確になる +- BFSは最短経路、DFSは連結成分・トポロジカルソート・サイクル検出などに応用しやすい +- UnionFindでも解けるが、問題側の操作に合った抽象を用意しないと使う側の負担が大きくなる + - 汎用的なUnionFindは自然数の添字同士をつなぐだけなので、呼び出し側が`row * num_cols + col`への変換や陸地判定を担当することになる + - 問題寄りのAPIを用意すると、呼び出し側が楽に書ける +- メソッドは呼び出し側に強い前提を押しつけず、多少雑に呼ばれても扱える設計が望ましい + +
From 7e7d35227da9ba66cec645c7722f68983e684459 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 2 May 2026 05:28:19 +0900 Subject: [PATCH 21/40] 695. Max Area of Island --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index d32ce55..00a91d8 100644 --- a/README.md +++ b/README.md @@ -175,3 +175,14 @@ - メソッドは呼び出し側に強い前提を押しつけず、多少雑に呼ばれても扱える設計が望ましい + +
+695. Max Area of Island + +- 島の最大面積も、グリッド上の連結成分をDFS/BFSで探索して各成分のサイズを数えればよい +- stackやqueueに同じマスを重複して積みたくない場合、`seen`は「処理済み」ではなく「発見済み」として、stack/queueに入れるタイミングで更新する +- `seen`をpop後に更新する書き方もできるが、その場合はpop直後に処理済みならskipしないと面積を二重に数えてしまう +- UnionFindでも解けるが、各マスは上下左右すべてではなく右と下だけを見れば隣接関係を重複なく結合できる +- どの解法でも全マスを確認する必要があるため、時間計算量はO(mn) + +
From 6e9c1b22d79a503d00cd33e80750b40500927c0f Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 2 May 2026 10:44:02 +0900 Subject: [PATCH 22/40] 127. Word Ladder --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00a91d8..9c1cebf 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ - ロジックの流暢さ -
373.Find K Pairs with Smallest Sums @@ -186,3 +185,16 @@ - どの解法でも全マスを確認する必要があるため、時間計算量はO(mn)
+ +
+127. Word Ladder + +- 単語をノード、1文字だけ違う関係を辺と見ればグラフの最短経路問題として扱える +- 全単語ペアを比較して隣接リストを作ると、前処理がO(N^2 L)になって遅い +- `subword`を使って`h*t`のような形から隣接候補を引くと、全ペア比較を避けられる +- 各位置を`a-z`に置き換えて候補を作り、`set`で存在確認する方法もある +- `set`は未訪問の有効単語集合としても使えるため、見つけた単語を削除すれば`seen`を別に持たずに済む +- `discard`は存在しない値を削除しようとしても何もしないが、`remove`は`KeyError`になる +- 双方向BFSでは常に小さいfrontierから広げることで探索量を減らせる + +
From ea932ca6b2e821390eaa74e461546df07b241ac6 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 2 May 2026 14:20:51 +0900 Subject: [PATCH 23/40] 104. Maximum Depth of Binary Tree --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 9c1cebf..38ab615 100644 --- a/README.md +++ b/README.md @@ -198,3 +198,47 @@ - 双方向BFSでは常に小さいfrontierから広げることで探索量を減らせる + +
+104. Maximum Depth of Binary Tree + +- 二分木の最大深さは、左右の部分木の最大深さを求めて大きい方に1を足すボトムアップ再帰で自然に書ける +- トップダウン再帰では、現在の深さを引数で渡しながら全ノードをたどり、外側の`max_depth`を更新する +- 内側関数から外側の変数に再代入する場合は`nonlocal`が必要になる + - 外側の変数を参照するだけなら`nonlocal`は不要 + ```python + def outer(): + x = 1 + + def inner(): + return x + + return inner() + ``` + - しかし、同じ内側関数内で代入すると、Pythonはその名前を内側関数のローカル変数とみなす + ```python + def outer(): + x = 1 + + def inner(): + x = x + 1 # UnboundLocalError + return x + + return inner() + ``` + - 外側の変数を更新したい場合は`nonlocal`で明示する + ```python + def outer(): + x = 1 + + def inner(): + nonlocal x + x = x + 1 + return x + + return inner() + ``` + - `max_depth = max(max_depth, depth)`も同じで、`nonlocal`なしだと右辺の`max_depth`まで内側関数のローカル変数扱いになり、代入前参照で`UnboundLocalError`になる +- LeetCodeの指定シグネチャにある引数名は、Pythonではkeyword呼び出しのAPIにもなり得るため、安易に変えない方がよい + +
From 937ab0a4c34b87de1edc62973b48d399d10ac825 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 2 May 2026 19:31:33 +0900 Subject: [PATCH 24/40] 111. Minimum Depth of Binary Tree --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 38ab615..26ef314 100644 --- a/README.md +++ b/README.md @@ -242,3 +242,15 @@ - LeetCodeの指定シグネチャにある引数名は、Pythonではkeyword呼び出しのAPIにもなり得るため、安易に変えない方がよい + +
+111. Minimum Depth of Binary Tree + +- 最小深さは根から浅い順に見るBFSと相性がよく、最初に見つけた葉の深さをそのまま返せる +- DFSで解く場合は、全探索しながら最小値を更新するトップダウン再帰か、左右の部分木の答えを集約するボトムアップ再帰で考えられる + - トップダウンは、現在の深さや根からの累積情報を引数で子へ配る + - ボトムアップは、書きやすい +- 再帰DFSは木が深いとコールスタック上限に当たる可能性があるため、深い入力があり得るならBFSや明示的なstackも検討する +- `None`だけを判定したい場合は、PEP 8やGoogle Python Style Guideの考え方に沿って`if node.left is None:`のように書く + +
From 2755ad1884a538f99441cd61fd1408c0cc312df7 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 2 May 2026 20:01:59 +0900 Subject: [PATCH 25/40] 617. Merge Two Binary Trees --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 26ef314..1c7ff87 100644 --- a/README.md +++ b/README.md @@ -254,3 +254,12 @@ - `None`だけを判定したい場合は、PEP 8やGoogle Python Style Guideの考え方に沿って`if node.left is None:`のように書く + +
+617. Merge Two Binary Trees + +- `copy.copy`は浅いコピーなので、親ノードだけが新しくなり、`left`や`right`の子孫ノードは元の木と共有される +- `copy.deepcopy`は深いコピーなので、`left`や`right`からたどれる子孫ノードも再帰的に複製される +- immutableなオブジェクトでは浅いコピーと深いコピーの差はほぼ問題にならないが、listやTreeNodeのように内部に別オブジェクトへの参照を持つ複合オブジェクトでは差が出る + +
From 53b109d9f8a47ddc943c7b54565f9c4f2ea65510 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 09:26:45 +0900 Subject: [PATCH 26/40] 112. Path Sum --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1c7ff87..531b61e 100644 --- a/README.md +++ b/README.md @@ -263,3 +263,13 @@ - immutableなオブジェクトでは浅いコピーと深いコピーの差はほぼ問題にならないが、listやTreeNodeのように内部に別オブジェクトへの参照を持つ複合オブジェクトでは差が出る + +
+112. Path Sum + +- 問題自体は再帰で素直に解ける +- パスを返す/数える発展 + - 経路自体を返したい場合は、各ノードで「親ノード」または「そこまでの累積和」を辞書/setに記録しておけば、目的の葉から遡って経路を再構成できる + - 経路の総数や全列挙が必要なら、early returnせずに最後まで探索し、累積和の出現回数を`defaultdict(int)`で集計するのが良い + +
From 928e92e2bb4e69ed2e2876cc41d56b9ee51e2d3c Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 10:23:37 +0900 Subject: [PATCH 27/40] 102. Binary Tree Level Order Traversal --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 531b61e..0abd40f 100644 --- a/README.md +++ b/README.md @@ -273,3 +273,41 @@ - 経路の総数や全列挙が必要なら、early returnせずに最後まで探索し、累積和の出現回数を`defaultdict(int)`で集計するのが良い + +
+102. Binary Tree Level Order Traversal + +複数の解法が考えられる。 + +- 帰りがけDFS再帰(部分木の結果をmergeする) + - 左右の部分木のlevel orderを再帰で求めてから、`itertools.zip_longest`で同レベル同士を連結する + - 時間計算量: 偏った木でO(n^2)(各レベルで`left + right`のリスト連結がかさむ) + - 空間計算量: 結果のO(n) + 再帰スタックのO(h) + - 計算量も悪化するので、積極的に選ぶ理由は薄い + +- 行きがけDFS再帰(レベルを引数で配る) + - `level`を引数で渡し、`level_order[level]`に値を直接appendする + - 時間計算量: O(n) + - 空間計算量: 結果のO(n) + 再帰スタックのO(h) + - 短く書けるが、深い木ではPythonの`sys.setrecursionlimit`に当たる/Cスタックを破壊するリスクがある + +- 行きがけDFS反復(明示stack) + - 上を明示スタックに書き直したもの。`deque.pop()`でDFS + - 左から処理したい場合、子は`(node.right, node.left)`の順で積む(後入れ先出しのため) + - 時間計算量: O(n) + - 空間計算量: 結果のO(n) + スタックのO(h) + - 再帰深さの上限を気にせず使え、ログも取りやすい + +- BFS(queue + level_size) + - 各反復で`len(queue)`を先に保存し、その回数だけ`popleft`して現レベルを取り出す + - 時間計算量: O(n) + - 空間計算量: 結果のO(n) + キューの最大幅(最悪O(n)) + - 「レベルごとの処理」が問題の構造そのものと一致するため最も読みやすい + - 最短深さや「最初に条件を満たす階層」を返したい派生問題にも素直に拡張できる + +- 例外処理は「if-elseで両方の場合を書く」より「ifで例外だけ早期returnし、本流のロジックを後ろにフラットに書く」のが読みやすいことがある +- 再帰の上限はPythonでは`sys.setrecursionlimit`が言語処理系側でガードする。Javaのスタックは~1MB、Cは~10MBが目安 +- 再帰は「ある条件のときだけログを出して呼び出し元を追う」のが難しい。ループに直すとデバッグしやすい +- クイックソートで小さい方を再帰、大きい方を末尾再帰で処理するのも、スタック深さ抑制のため + +
From 893efbcdc34fe26b9b47750766ec325162bea82e Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 10:48:06 +0900 Subject: [PATCH 28/40] 103. Binary Tree Zigzag Level Order Traversal --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 0abd40f..d6e6da6 100644 --- a/README.md +++ b/README.md @@ -311,3 +311,15 @@ - クイックソートで小さい方を再帰、大きい方を末尾再帰で処理するのも、スタック深さ抑制のため + +
+103. Binary Tree Zigzag Level Order Traversal + +- 102(level order)に「奇数段だけ向きを反転」を足した派生問題と捉えると、102のBFS解にreverseを1行足すだけで済む +- リストを「逆順」に扱う方法は用途で選ぶ + - `xs.reverse()`:破壊的(in-place)。リスト自体を反転して構わない場面で最有力 + - `reversed(xs)`:非破壊の逆順イテレータ。`for`で回すだけならコピー不要で軽い(返り値はイテレータ) + - `list(reversed(xs))` / `xs[::-1]`:非破壊で「反転した新しいリスト」が欲しいとき(どちらも O(n) コピー) + - 手動の`for i in range(len(xs)-1, -1, -1)`:特殊な制御が要るときだけ。可読性は落ちがち + +
From 7f5d476f5a65862ffe1783493b490da3e816c0bb Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 10:58:05 +0900 Subject: [PATCH 29/40] 108. Convert Sorted Array to Binary Search Tree --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6e6da6..494bd4c 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,13 @@ +108. Convert Sorted Array to Binary Search Tree + +- ソート済み配列の中央を根にして、左右の半分から部分木を再帰的に作るのが一番素直 +- スライス `nums[:idx]`, `nums[idx+1:]` で書くとシンプルだが、コピーが発生するため空間計算量がO(n log n)になる + + +
102. Binary Tree Level Order Traversal @@ -310,7 +317,6 @@ - 再帰は「ある条件のときだけログを出して呼び出し元を追う」のが難しい。ループに直すとデバッグしやすい - クイックソートで小さい方を再帰、大きい方を末尾再帰で処理するのも、スタック深さ抑制のため -
103. Binary Tree Zigzag Level Order Traversal From 245daa4ffb06ceb9f70db795141b963fab927055 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 15:22:10 +0900 Subject: [PATCH 30/40] 300. Longest Increasing Subsequence --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 494bd4c..fc3ba22 100644 --- a/README.md +++ b/README.md @@ -329,3 +329,20 @@ - 手動の`for i in range(len(xs)-1, -1, -1)`:特殊な制御が要るときだけ。可読性は落ちがち
+ +
+300. Longest Increasing Subsequence + +- 愚直DPだと計算量 O(n^2) +- 二分探索でも解ける O(n log n) +- セグメント木: 元の DP 漸化式 の `max` を区間 max クエリで O(log n) に高速化。全体 O(n log n) + - 「値の小ささ」は座標圧縮した index の範囲で、「並び順の前後」は `nums` を順に for で回すことで、別々に分担させる + - 1-indexed のセグメント木では「奇数 = 右の子、偶数 = 左の子」となり、区間 max のループ条件が綺麗に書ける +- Binary Indexed Tree (Fenwick Tree): セグメント木の特殊版で、「常に左端からの累積(prefix)」だけを扱うと決めれば配列サイズを半分(n+1)にできる + - `index & -index` で親/子に O(1) で移れる(lowbit)。実装は短いが、prefix 系の操作しかできないのが制約 +- bisect の挙動 + - `bisect_left(a, x)`: `a[:i]` がすべて `< x` となる最小の `i`(既存の `x` の左) + - `bisect_right(a, x)`: `a[:i]` がすべて `<= x` となる最小の `i`(既存の `x` の右) + - `a` に `x` がなければ両者は一致 + +
From 37e8c9d847b272a754dd9761de848e795ec1fab5 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sun, 3 May 2026 22:12:11 +0900 Subject: [PATCH 31/40] 53. Maximum Subarray --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index fc3ba22..937e223 100644 --- a/README.md +++ b/README.md @@ -346,3 +346,15 @@ - `a` に `x` がなければ両者は一致 + +
+53. Maximum Subarray + +- 「ある位置 `i` を末尾とする部分配列の最大和」を順に求めれば、その全体の最大が答えになる、という発想で O(n) になる +- 同じことを2通りの式で書ける(どちらも O(n)、空間 O(1)) + - prefix sum + - `min_cumulative_sum` の初期値を 0 にすることで、「途中から始める」と「先頭から始める」を統一して扱える + - Kadane版のアルゴリズム + - 直感的には「過去の合計がマイナスなら相続をやめる」 + +
From 09dd6616babda14c7250006e3a6b6170bbd94b77 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 05:03:07 +0900 Subject: [PATCH 32/40] 62. Unique Paths --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 937e223..f53fefc 100644 --- a/README.md +++ b/README.md @@ -358,3 +358,15 @@ - 直感的には「過去の合計がマイナスなら相続をやめる」 + +
+62. Unique Paths + +- メモ化再帰で`functools.cache` を使うと再帰関数のメモ化を1行で書ける +- Python のリスト掛け算 + - `[1] * 10` のように immutable な要素を掛け算で並べるのは安全 + - `[[1]] * 10` のように mutable オブジェクトを掛け算で並べると、全要素が同じオブジェクトを指すため一箇所の変更が全体に波及する + - 2次元配列を作るときは `[[1] for _ in range(n)]` のように内包表記で個別生成するのが安全 + - リストはオブジェクトへのポインタを持っており、immutable は再代入で別オブジェクトに差し替わるが mutable はその場で書き換わる、という違いが原因 + +
From ac8fe4c3a37035bda8132efb5acef4eaad35bdad Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 05:28:54 +0900 Subject: [PATCH 33/40] 63. Unique Paths II --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index f53fefc..3df9646 100644 --- a/README.md +++ b/README.md @@ -370,3 +370,16 @@ - リストはオブジェクトへのポインタを持っており、immutable は再代入で別オブジェクトに差し替わるが mutable はその場で書き換わる、という違いが原因 + + +
+63. Unique Paths II + +- 配る DPと もらう DP + - もらう DP: `dp[r][c]` を「自分が上 / 左から受け取る値」で計算する(一般的) + - 配る DP: `dp[r][c]` の値を「右と下のマスへ加算する」形で書く +- コンパイラ言語とインタプリタ言語での `if` 分岐コストの違い + - C / Rust などのコンパイラ言語では、分岐予測失敗で命令パイプラインのやり直しが発生するため、`for` の中の `if` はなるべく減らす方が速い + - 「0 行目/0 列目だけの特殊処理」など事前に決まっている分岐は、ループの外に出して別ループで処理した方が、可読性も速度も向上する + - Python はインタプリタ実行で元から大量の分岐命令を踏むので、ここを気にしても差は出にくい(可読性の観点では分けてもよい) +
From ece3b484c12051b3494c0f271442b0b7756f3663 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 06:55:40 +0900 Subject: [PATCH 34/40] 198. House Robber --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 3df9646..c43fa67 100644 --- a/README.md +++ b/README.md @@ -383,3 +383,13 @@ - 「0 行目/0 列目だけの特殊処理」など事前に決まっている分岐は、ループの外に出して別ループで処理した方が、可読性も速度も向上する - Python はインタプリタ実行で元から大量の分岐命令を踏むので、ここを気にしても差は出にくい(可読性の観点では分けてもよい) + +
+198. House Robber + +- DP配列のメモリは O(n) → O(1) に落とせる +- 変数の意味はコメントで明示すると読みやすい +- メモ化再帰は `@functools.cache` を使うと1行で書ける +- `functools.lru_cache` / `functools.cache` のキャッシュはスレッドセーフ + - 内部でロックを取っているので、複数スレッドから同じ関数を実行してもキャッシュ自体の状態は壊れない +
From 4efa998ea0069357b4d6a44dd086b1a69b798127 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 07:11:59 +0900 Subject: [PATCH 35/40] 213. House Robber II --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index c43fa67..f48316a 100644 --- a/README.md +++ b/README.md @@ -393,3 +393,27 @@ - `functools.lru_cache` / `functools.cache` のキャッシュはスレッドセーフ - 内部でロックを取っているので、複数スレッドから同じ関数を実行してもキャッシュ自体の状態は壊れない + +
+213. House Robber II + +- 円環の制約は「場合分けで消す」ことで直線版に帰着できる + - 思考の型: 「未知のものは何か」「与えられているものは何か」「条件は何か」を整理し、厄介な条件を場合分けで消せないか考える +- DP の用語の整理 + - DP は「部分構造最適性」と「部分問題の重複」を持つ問題に対し、「一度計算した部分問題は保存して使い回す」設計指針の総称 + - 実現方法は2つ + - **Tabulation (タビュレーション)**: ボトムアップ・反復型。DP 配列を小さい方から埋めていく + - **Memoization (メモ化再帰)**: トップダウン・再帰型。必要になった部分問題を再帰で計算しキャッシュする +- スワップを多重代入で書く(再掲) + - `a, b = new_a, new_b` の形にすれば、入れ替え用の一時変数を持たずに同時更新できる +- `functools.reduce` で副作用のない関数型スタイルに置き換えられる + - `reduce(func, iterable, initial)` は他言語でいう `fold_left` と同じで、状態を引数として持ち回り再代入を消せる + - 可読性は落ちる代わり、ループ内の代入による副作用が無くなり、状態遷移を関数として切り出せる +- `itertools.islice` でスライスのコピーを避ける(`sol2.py`) + - 新しいリストを作る場合 O(k) のコピーが発生する + - `itertools.islice(nums, 1, len(nums))` は元のリストをコピーせず遅延評価のイテレータを返すので、メモリも時間も節約できる + - 引数として一度走査するだけで済むようなケースでは、スライスより `islice` が向いている +- list slice の計算量 + - `nums[a:b]` は要素 `b - a` 個ぶんの新しいリストを作るため O(b - a) の時間と空間がかかる + - に基本演算の計算量がまとまっている +
From 46c9be5f89db07c4925f59ca39f7e5682581adb6 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 07:33:22 +0900 Subject: [PATCH 36/40] 121. Best Time to Buy and Sell Stock --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index f48316a..ccdfc7a 100644 --- a/README.md +++ b/README.md @@ -417,3 +417,18 @@ - `nums[a:b]` は要素 `b - a` 個ぶんの新しいリストを作るため O(b - a) の時間と空間がかかる - に基本演算の計算量がまとまっている + +
+121. Best Time to Buy and Sell Stock + +- `itertools.accumulate(prices, min)` で「その日までの最小値」を遅延列として作れる(`sol3.py`) + - Haskell の `scanl1 min` 相当。関数型風に「最小列 → 利益列 → max」と書ける + - + - 同じ問題を「scanl で累積最小、zipWith (-) で利益、max で集約」と分解できる +- import スタイル(Google Python Style Guide) + - `from itertools import islice` のように個別の関数を import するのではなく、`import itertools` してから `itertools.islice(...)` と書く方が好まれる + - > Use import statements for packages and modules only, not for individual types, classes, or functions. +- 型ヒント + - Python 3.9+ では `typing.List[int]` より組み込みの `list[int]` が推奨 + - 引数はより抽象的な型(`Sequence`, `Iterable`)、返り値はより具体的な型(`list[int]`)にすると、呼び出し側の自由度を上げつつ、利用側に確かな情報を返せる +
From b88e7fdbb7bb391dac7fcca49f311fe578ddfee3 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 08:24:56 +0900 Subject: [PATCH 37/40] 122. Best Time to Buy and Sell Stock II --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ccdfc7a..46fdaf3 100644 --- a/README.md +++ b/README.md @@ -432,3 +432,8 @@ - Python 3.9+ では `typing.List[int]` より組み込みの `list[int]` が推奨 - 引数はより抽象的な型(`Sequence`, `Iterable`)、返り値はより具体的な型(`list[int]`)にすると、呼び出し側の自由度を上げつつ、利用側に確かな情報を返せる + +
+122. Best Time to Buy and Sell Stock II +- 答えは \( \sum_{k=1}^{n-1}\max(0, p_k - p_{k-1}) \) と書ける +
From 46526980f533a0f461258135e44c7ab9eecd3f88 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 08:50:15 +0900 Subject: [PATCH 38/40] 139. Word Break --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 46fdaf3..0328fac 100644 --- a/README.md +++ b/README.md @@ -437,3 +437,20 @@ 122. Best Time to Buy and Sell Stock II - 答えは \( \sum_{k=1}^{n-1}\max(0, p_k - p_{k-1}) \) と書ける + +
+139. Word Break + +- `s[start:].startswith(word)` のようなスライスは、毎回新しい文字列オブジェクトを生成する O(n−start) のコピー が発生する。代わりに `s.startswith(word, start)` を使えば追加メモリなしで比較できる。同様に `s[start:end] == word` よりも `s.startswith(word, start)` の方が速い。 +- `str.startswith` は tuple を受け取れる + +```python +s.startswith(("apple", "pen")) # どれかで始まれば True +``` + +- 正規表現的な見方 + - `((apple)|(pen))*` のように正規表現で書ける問題は、`wordDict` を定数とみなせば NFA/DFA 的に O(n) で解ける +- `dataclasses.field(default_factory=...)` + - `list` / `dict` / `set` のような mutable をフィールドのデフォルト値に直接書くと、`@dataclass` は `TypeError` を出す。インスタンスごとに独立した mutable を持たせたい場合は `default_factory` を使う + +
From dfcb74ca574355e29b0095cacdef17ab98ad96a9 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 09:28:36 +0900 Subject: [PATCH 39/40] 322. Coin Change --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 0328fac..906ada2 100644 --- a/README.md +++ b/README.md @@ -454,3 +454,13 @@ s.startswith(("apple", "pen")) # どれかで始まれば True - `list` / `dict` / `set` のような mutable をフィールドのデフォルト値に直接書くと、`@dataclass` は `TypeError` を出す。インスタンスごとに独立した mutable を持たせたい場合は `default_factory` を使う + +
+322. Coin Change + +- `math.isinf`を用いた判定 +- `min` を取るときの工夫 + - 子の結果を generator で流し、`min(gen, default=-1)` で集約すると、無効値を一切流さずに書ける + - `min` / `max` は `default` 引数で「空イテラブルのときの値」を指定できる + +
From b072485265176681135d9e64ca7312ea82ac1966 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 4 May 2026 09:41:23 +0900 Subject: [PATCH 40/40] Solve again --- {35 => 35.Search-Insert-Position}/memo.md | 8 ++++---- {35 => 35.Search-Insert-Position}/sol1.py | 0 {35 => 35.Search-Insert-Position}/sol2.py | 0 {35 => 35.Search-Insert-Position}/sol3.py | 0 {35 => 35.Search-Insert-Position}/sol4.py | 0 35.Search-Insert-Position/sol5_retry.py | 6 ++++++ 35.Search-Insert-Position/sol6_retry.py | 11 +++++++++++ 7 files changed, 21 insertions(+), 4 deletions(-) rename {35 => 35.Search-Insert-Position}/memo.md (94%) rename {35 => 35.Search-Insert-Position}/sol1.py (100%) rename {35 => 35.Search-Insert-Position}/sol2.py (100%) rename {35 => 35.Search-Insert-Position}/sol3.py (100%) rename {35 => 35.Search-Insert-Position}/sol4.py (100%) create mode 100644 35.Search-Insert-Position/sol5_retry.py create mode 100644 35.Search-Insert-Position/sol6_retry.py diff --git a/35/memo.md b/35.Search-Insert-Position/memo.md similarity index 94% rename from 35/memo.md rename to 35.Search-Insert-Position/memo.md index e951ad2..2e32802 100644 --- a/35/memo.md +++ b/35.Search-Insert-Position/memo.md @@ -89,9 +89,9 @@ i >= right → flag_nums[i] == True ### ループ不変式: -i < left -> nums[i] <= target (1) +i < left -> nums[i] < target (1) -&& j >= right -> nums[j] > target (2) +&& j >= right -> nums[j] >= target (2) ### 成り立つことの確認 @@ -100,8 +100,8 @@ i < left -> nums[i] <= target (1) i < left, j >= rightとなるi, jは存在しない。 #### 維持: ある反復で成り立つと仮定して次の反復で成り立つことを示す。場合分け。 -- nums[middle] <= target の場合: numsがソートされているため left=middle+1とした場合、(1) は依然として成立 -- nums[middle] > target の場合 numsがソートされているため right=middle とした場合(2) は依然として成立 +- nums[middle] < target の場合: numsがソートされているため left=middle+1とした場合、(1) は依然として成立 +- nums[middle] >= target の場合 numsがソートされているため right=middle とした場合(2) は依然として成立 #### 終了: left < rightがFalseとなる。更新式よりleft==rightである (1),(2)と合わせて、 diff --git a/35/sol1.py b/35.Search-Insert-Position/sol1.py similarity index 100% rename from 35/sol1.py rename to 35.Search-Insert-Position/sol1.py diff --git a/35/sol2.py b/35.Search-Insert-Position/sol2.py similarity index 100% rename from 35/sol2.py rename to 35.Search-Insert-Position/sol2.py diff --git a/35/sol3.py b/35.Search-Insert-Position/sol3.py similarity index 100% rename from 35/sol3.py rename to 35.Search-Insert-Position/sol3.py diff --git a/35/sol4.py b/35.Search-Insert-Position/sol4.py similarity index 100% rename from 35/sol4.py rename to 35.Search-Insert-Position/sol4.py diff --git a/35.Search-Insert-Position/sol5_retry.py b/35.Search-Insert-Position/sol5_retry.py new file mode 100644 index 0000000..1a82352 --- /dev/null +++ b/35.Search-Insert-Position/sol5_retry.py @@ -0,0 +1,6 @@ +import bisect + + +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + return bisect.bisect_left(nums, target) diff --git a/35.Search-Insert-Position/sol6_retry.py b/35.Search-Insert-Position/sol6_retry.py new file mode 100644 index 0000000..3b424fb --- /dev/null +++ b/35.Search-Insert-Position/sol6_retry.py @@ -0,0 +1,11 @@ +class Solution: + def searchInsert(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) + while left < right: + middle = left + (right - left) // 2 + if nums[middle] < target: + left = middle + 1 + else: + right = middle + return left