diff --git a/.markdownlint.json b/.markdownlint.json index da5c719..8df6452 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,10 +1,10 @@ { "default": true, - "MD001": false, + "MD001": true, "MD002": true, "MD003": { "style": "atx" }, "MD004": { "style": "consistent" }, - "MD007": { "indent": 4 }, + "MD007": { "indent": 2 }, "MD009": { "br_spaces": 2 }, "MD012": true, "MD013": { @@ -28,9 +28,10 @@ "ol_multi": 1 }, "MD031": true, - "MD032": true, + "MD032": false, "MD033": { "allowed_elements": [ + "a", "h1", "h2", "p", @@ -53,5 +54,6 @@ "MD042": false, "MD046": { "style": "fenced" }, "MD048": { "style": "backtick" }, - "MD058": false + "MD058": false, + "MD060": false } diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Python.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Python.md new file mode 100644 index 0000000..61c3f44 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Python.md @@ -0,0 +1,337 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: Python (CPython 3.11.10) +> 適甚ルヌルセット: 共通5ルヌル + Python固有ルヌル +> 参照ファむル: references/common.md + references/python.md + +--- + +# 1. 問題分析結果 + +> 💡 **䞀蚀で蚀うず**「2皮類の"朚の探玢順リスト"を手がかりに、元の二分朚を Python のクラスむンスタンスずしお埩元する問題」です。 + +## Python で解く際の CPython 特有の泚意点 + +Python の `list.index()` メ゜ッドは内郚が C 実装で高速に芋えたすが、O(n) の線圢探玢先頭から1぀ず぀比范する探玢であるこずに倉わりありたせん。毎回呌び出すず党䜓で O(n²) になりたす。代わりに `dict`ハッシュマップキヌから倀を O(1) で取り出せる蟞曞を前凊理で䜜るこずで、党䜓を O(n) に抑えられたす。たた、Python の再垰はデフォルトで深さ 1000 たでに制限されおいたす。本問の制玄最倧 3000 ノヌドでは最悪 3000 段の再垰完党に偏った朚が起きうるため、`sys.setrecursionlimit` での䞊限緩和も業務版では考慮したす。 + +--- + +### 競技プログラミング芖点 + +- **制玄分析**: `n ≀ 3000` → O(n²) は 9,000,000 回の操䜜で TLE制限時間超過の可胜性あり。O(n) が必須 +- **最速手法**: `dict` による前凊理 + 再垰。`nonlocal`内偎の関数から倖偎の倉数を曞き換えるキヌワヌドでカヌ゜ルを共有 +- **CPython 最適化**: `dict.__getitem__` は C 実装で O(1)。`sys.setrecursionlimit` で再垰制限を緩和 + +### 業務開発芖点 + +- **型安党蚭蚈**: `List[int]`・`Optional[TreeNode]` で pylance ゚ラヌなし +- **゚ラヌハンドリング**: 長さ䞍䞀臎・空入力を `ValueError` で早期怜出 +- **可読性**: ヘルパヌメ゜ッドに分割し責務を明確化 + +### Python 特有分析 + +| 芳点 | 競技版 | 業務版 | +| ------------ | ----------------------- | ---------------------------------- | +| デヌタ構造 | `dict` + `list` | `dict` + `list` | +| 再垰制限察応 | `sys.setrecursionlimit` | `sys.setrecursionlimit` + コメント | +| 型ヒント | 最小限 | 完党 pylance 察応 | +| ゚ラヌ凊理 | 省略 | `ValueError` / `TypeError` | + +> 📖 **このセクションで登堎した甚語** +> +> - **CPython**: 最も広く䜿われる Python 実装。C 蚀語で曞かれおおり、`dict` などの組み蟌み型は C 実装のため高速 +> - **線圢探玢**: 先頭から1぀ず぀比范しお目的の倀を探す方法。リストのサむズに比䟋しお時間がかかる +> - **`nonlocal`**: 内偎の関数から、倖偎でも `global` ではないのスコヌプにある倉数を曞き換えるための Python キヌワヌド +> - **TLETime Limit Exceeded**: 制限時間内に凊理が終わらない゚ラヌ + +--- + +# 2. 採甚アルゎリズムず根拠 + +> 💡 同じ問題でも解き方は耇数ありたす。それぞれの「速さ」ず「メモリ䜿甚量」を比べおから最適なものを遞びたす。Python では「C 実装かどうか」も重芁な遞択基準です。 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | Python実装コスト | 可読性 | CPython最適化 | 備考 | +| ---------------------------- | ---------- | ---------- | ---------------- | ------ | ----------------------------------- | ---------------------------- | +| **A: 再垰 + `list.index()`** | O(n²) | O(n) | 䜎 | ★★★ | 䞍適毎回C実装でも O(n) | n=3000で最悪9M操䜜 | +| **B: 再垰 + `dict` 前凊理** | **O(n)** | O(n) | äž­ | ★★★ | 適`dict`ルックアップはC実装O(1) | ★最適 | +| **C: 反埩 + スタック** | O(n) | O(n) | 高 | ★★☆ | 適 | 実装耇雑・再垰制限回避できる | + +**遞択: B再垰 + `dict` 前凊理** + +- **A を遞ばなかった理由**: `list.index()` は C 実装で高速ですが、それでも O(n) の線圢探玢。n=3000 のずき最悪 9,000,000 回の比范が発生したす +- **C を遞ばなかった理由**: `TreeNode` の `left`/`right` を埌から蚭定するスタック管理が耇雑で、可読性が倧きく䜎䞋したす +- **Python 最適化戊略**: `dict` の `__getitem__` は CPython の C 実装で平均 O(1)。`nonlocal` で再垰間のカヌ゜ル共有を実珟 + +> 📖 **このセクションで登堎した甚語** +> +> - **`dict`蟞曞**: キヌから倀を平均 O(1) で取り出せる Python の組み蟌みデヌタ構造。内郚はハッシュテヌブル倀の堎所を蚈算で盎接求める仕組み +> - **時間蚈算量**: 入力の倧きさに察しお凊理にかかる手間がどう増えるかの目安 +> - **空間蚈算量**: 凊理䞭に䜿うメモリ量がどう増えるかの目安 +> - **C 実装**: Python コヌドではなく C 蚀語で曞かれた関数。Pure Python より倧幅に高速 + +--- + +# 3. 実装パタヌン + +## コヌドの骚栌先に党䜓像を把握する + +```text +1. 入力怜蚌: 長さ䞍䞀臎・空リストを早期怜出 +2. 前凊理: inorder の「倀 → むンデックス」dict を O(n) で構築 +3. preorder カヌ゜ル idx を nonlocal で再垰間共有する準備 +4. 再垰関数 build(left, right): + a. left > right → None を返す郚分朚なし・終了条件 + b. preorder[idx] をルヌト倀ずしお取埗、idx を進める + c. dict でルヌトの inorder 䜍眮を O(1) で取埗 + d. TreeNode を生成、巊右を再垰的に構築しお接続 +5. build(0, n-1) を呌び出しお返す +``` + +--- + +## 【業務開発版を䜿う堎面】 + +チヌムで長期間メンテナンスするプロダクションコヌドに向きたす。゚ラヌの原因が分かりやすく、pylance による静的型チェックも通る構造になっおいたす。再垰制限の緩和理由もコメントで明瀺し、埌から読んだ人が意図を理解できるようにしたす。 + +```python +from __future__ import annotations # アノテヌションを遅延評䟡にするPEP 563 + +import sys +from typing import TYPE_CHECKING, Optional + +# LeetCode 環境倖でも import/実行できるよう最小限の TreeNode を定矩する。 +# LeetCode では実行時に TreeNode が泚入されるため、NameError にならない堎合はスキップ。 +try: + TreeNode # type: ignore[name-defined] +except NameError: + class TreeNode: # type: ignore[no-redef] + def __init__( + self, + val: int = 0, + left: Optional[TreeNode] = None, + right: Optional[TreeNode] = None, + ) -> None: + self.val = val + self.left = left + self.right = right + +# 再垰深床の䞊限を緩和する。 +# Python デフォルトは 1000 だが、本問は最悪 3000 段偏った朚になりうる。 +# この蚭定は Solution クラスの倖に眮くこずでモゞュヌルロヌド時に1回だけ実行される。 +sys.setrecursionlimit(10_000) + + +class Solution: + def buildTree( + self, + preorder: list[int], + inorder: list[int], + ) -> Optional[TreeNode]: + """ + preorder前順ず inorder䞭順から二分朚を埩元する。 + + Args: + preorder: 前順探玢の配列先頭が必ずルヌト + inorder: 䞭順探玢の配列ルヌトの巊右を区切る境界線 + + Returns: + 埩元した二分朚のルヌトノヌド。空の堎合は None。 + + Raises: + ValueError: 2぀の配列の長さが異なる堎合 + TypeError: 匕数がリストでない堎合 + + Complexity: + Time: O(n) ─ 各ノヌドをちょうど1回だけ凊理 + Space: O(n) ─ dictn ゚ントリ+ 再垰スタック高さ h 分 + """ + # ① 型チェックlist 以倖が枡された堎合に分かりやすい゚ラヌを出す。 + # Python は動的型付けなので、実行時たで型゚ラヌに気づかない。 + # pylance ず合わせるこずでコンパむル時盞圓の安党性を実珟する。 + if not isinstance(preorder, list) or not isinstance(inorder, list): + raise TypeError("Both preorder and inorder must be lists") + + # ② 長さ䞍䞀臎チェック2぀の配列が同じ朚を衚しおいない堎合は埩元䞍可。 + if len(preorder) != len(inorder): + raise ValueError( + f"Length mismatch: preorder={len(preorder)}, inorder={len(inorder)}" + ) + + # ③ 空リストチェックノヌドが1぀もない → 朚なし → None を返す。 + if not preorder: + return None + + # ④ 前凊理inorder の「倀 → むンデックス」を dict に O(n) で登録する。 + # なぜ dict かlist.index() は C 実装でも O(n) の線圢探玢のため、 + # n ノヌドで合蚈 O(n²) になる。dict なら __getitem__ が平均 O(1)。 + # 日垞の䟋え図曞通の玢匕カヌド。本のタむトル倀から棚番号むンデックスを即座に匕ける。 + inorder_index: dict[int, int] = {val: i for i, val in enumerate(inorder)} + + # â‘€ preorder を先頭から消費するカヌ゜ルを初期化する。 + # この実装では、リストでラップする手法の代わりに nonlocal な敎数カヌ゜ル (preorder_idx) を䜿甚しお preorder 芁玠を消費したす。 + preorder_idx = 0 + + def build(left: int, right: int) -> Optional[TreeNode]: + """ + inorder の [left, right] 範囲に察応する郚分朚を再垰的に構築する。 + + Args: + left: 今構築すべき郚分朚の inorder 䞊の巊端むンデックス + right: 今構築すべき郚分朚の inorder 䞊の右端むンデックス + """ + # nonlocal を䜿っお倖偎の preorder_idx を曞き換える宣蚀。 + # nonlocal なしで代入するず、Python は新しいロヌカル倉数ずしお扱い + # 倖偎の倉数が曎新されないバグが起きる。 + nonlocal preorder_idx + + # ⑥ 再垰の終了条件範囲が空 → 郚分朚なし → None を返す。 + if left > right: + return None + + # ⑩ preorder の珟圚䜍眮の倀がこの郚分朚のルヌトになる。 + # preorder は「ルヌト → å·Š → 右」の順なので、 + # 呌ばれた時点の先頭が必ずこの郚分朚のルヌト倀になる。 + root_val: int = preorder[preorder_idx] + preorder_idx += 1 # 次の再垰呌び出しのために進める + + # ⑧ dict でルヌト倀の inorder 䞊の䜍眮を O(1) で取埗する。 + # この䜍眮midを境に「巊偎 = 巊郚分朚」「右偎 = 右郚分朚」が決たる。 + mid: int = inorder_index[root_val] + + # ⑹ TreeNode を生成し、巊右を再垰的に構築しお接続する。 + # ★ 巊を先に構築する理由preorder は「ルヌト→巊→右」の順なので + # 巊の再垰が終わるたで右のルヌト倀は preorder に珟れない。 + node = TreeNode(root_val) + node.left = build(left, mid - 1) # 巊郚分朚mid の巊偎 + node.right = build(mid + 1, right) # 右郚分朚mid の右偎 + return node + + # ⑩ inorder 党䜓0 〜 n-1を察象ずしお朚党䜓を構築しお返す + return build(0, len(inorder) - 1) +``` + +--- + +## 【競技プログラミング版を䜿う堎面】 + +LeetCode の制限時間内に通すこずが目的のコヌドです。型チェックや゚ラヌハンドリングを省略し、最小限のコヌドで最速実行を目指したす。 + +```python +# Runtime 3 ms +# Beats 78.79% +# Memory 21.00 MB +# Beats 80.71% + +import sys +from typing import Optional + +sys.setrecursionlimit(10_000) + + +class Solution: + def buildTree( + self, preorder: list[int], inorder: list[int] + ) -> Optional[TreeNode]: + # inorder の「倀→むンデックス」を dict で前凊理O(n) + # dict 内包衚蚘1行で dict を䜜るPython固有の曞き方を䜿い簡朔に曞く + inorder_idx: dict[int, int] = {v: i for i, v in enumerate(inorder)} + preorder_pos = 0 # preorder のカヌ゜ル + + def build(lo: int, hi: int) -> Optional[TreeNode]: + nonlocal preorder_pos + if lo > hi: + return None + val = preorder[preorder_pos] + preorder_pos += 1 + mid = inorder_idx[val] + node = TreeNode(val) + node.left = build(lo, mid - 1) + node.right = build(mid + 1, hi) + return node + + return build(0, len(inorder) - 1) +``` + +--- + +## 動䜜トレヌス具䜓的な入力䟋 + +```text +入力: preorder = [3, 9, 20, 15, 7] + inorder = [9, 3, 15, 20, 7] + +【前凊理】inorder_index の構築dict 内包衚蚘で O(n): + {9: 0, 3: 1, 15: 2, 20: 3, 7: 4} + +preorder_idx = 0 で build(lo=0, hi=4) を呌び出す + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌱 build(lo=0, hi=4) + val = preorder[0] = 3, preorder_idx → 1 + mid = inorder_index[3] = 1 + node = TreeNode(3) + + ┌── node.left = build(lo=0, hi=0) ← inorder[0..0] = [9] + │ val = preorder[1] = 9, preorder_idx → 2 + │ mid = inorder_index[9] = 0 + │ node = TreeNode(9) + │ node.left = build(lo=0, hi=-1) → lo > hi → None + │ node.right = build(lo=1, hi=0) → lo > hi → None + │ return TreeNode(9) + │ + └── node.right = build(lo=2, hi=4) ← inorder[2..4] = [15, 20, 7] + val = preorder[2] = 20, preorder_idx → 3 + mid = inorder_index[20] = 3 + node = TreeNode(20) + + ┌── node.left = build(lo=2, hi=2) ← inorder[2..2] = [15] + │ val = preorder[3] = 15, preorder_idx → 4 + │ mid = inorder_index[15] = 2 + │ node = TreeNode(15) + │ node.left = build(lo=2, hi=1) → lo > hi → None + │ node.right = build(lo=3, hi=2) → lo > hi → None + │ return TreeNode(15) + │ + └── node.right = build(lo=4, hi=4) ← inorder[4..4] = [7] + val = preorder[4] = 7, preorder_idx → 5 + mid = inorder_index[7] = 4 + node = TreeNode(7) + node.left = build(lo=4, hi=3) → lo > hi → None + node.right = build(lo=5, hi=4) → lo > hi → None + return TreeNode(7) + + return TreeNode(20, left=TreeNode(15), right=TreeNode(7)) + +return TreeNode(3, left=TreeNode(9), right=TreeNode(20, ...)) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +最終結果の朚 + 3 + / \ + 9 20 + / \ + 15 7 +✅ Output: [3, 9, 20, null, null, 15, 7] +``` + +--- + +# 4. 怜蚌 + +> 💡 ゚ッゞケヌスずは「空リスト・芁玠が1぀・極端に偏った圢の朚」など通垞ずは異なる境界的な入力のこずです。゚ッゞケヌスのテストは、アルゎリズムが"ふ぀うの入力"だけでなく"極端な入力"でも正しく動くかを確かめるためのものです。 + +| テストケヌス | 入力 | 期埅出力 | 確認ポむント | +| ------------------ | ---------------------------------------- | ------------------------- | ------------------------ | +| 基本䟋 | `pre=[3,9,20,15,7]`, `ino=[9,3,15,20,7]` | `[3,9,20,null,null,15,7]` | 通垞の朚 | +| 芁玠1぀ | `pre=[-1]`, `ino=[-1]` | `[-1]` | 単䞀ノヌド巊右 None | +| 右に偏った朚 | `pre=[1,2,3]`, `ino=[1,2,3]` | `[1,null,2,null,3]` | 最倧再垰深さに近い圢 | +| 巊に偏った朚 | `pre=[3,2,1]`, `ino=[1,2,3]` | `[3,2,null,1]` | 逆方向の偏り | +| 負の倀を含む | `pre=[-3,9,-20]`, `ino=[9,-3,-20]` | `[-3,9,-20]` | 制玄範囲内の負倀 | +| 業務版: 型゚ラヌ | `pre="abc"`, `ino=[1]` | `TypeError` | 型ガヌドの動䜜確認 | +| 業務版: 長さ䞍䞀臎 | `pre=[1,2]`, `ino=[1]` | `ValueError` | バリデヌションの動䜜確認 | + +> 📖 **このセクションで登堎した甚語** +> +> - **゚ッゞケヌス**: 空のリスト・芁玠1぀・最倧サむズ入力など、境界的な条件のこず +> - **境界倀テスト**: ゚ッゞケヌスに察しおもアルゎリズムが正しく動くかを確かめるこず +> - **`nonlocal`**: 内偎の関数から倖偎のロヌカル倉数を曞き換えるための Python キヌワヌド。`global`モゞュヌル倉数ずは異なり、盎接倖偎の関数スコヌプを察象にする +> - **dict 内包衚蚘**: `{k: v for k, v in iterable}` ずいう圢で dict を1行で䜜る Python 固有の曞き方。`for` ルヌプより高速で可読性も高い diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Rust.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Rust.md new file mode 100644 index 0000000..c9a4815 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Rust.md @@ -0,0 +1,328 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: Rust +> 適甚ルヌルセット: 共通5ルヌル + Rust固有5ルヌル +> 参照ファむル: references/common.md + references/rust.md + +--- + +# 1. 問題の分析 + +> 💡 **䞀蚀で蚀うず**「前順・䞭順の2皮類の探玢リストから、`Rc>` ずいう Rust 特有のノヌド構造で二分朚を安党に埩元する問題」です。 + +## Rust で解く際に特に気を぀けるべき点 + +TypeScript 版ずの最倧の違いは、**ノヌドの型が `Option>>`** ずいう耇合型になっおいる点です。これは Rust の所有暩倀を"誰が管理するか"をコンパむル時に決める仕組みルヌルの制玄から来おいたす。朚のような「耇数の芪子関係を持぀構造」は玠盎に曞くず所有暩が競合するため、`Rc`参照カりント耇数箇所から同じ倀を参照できる仕組みず `RefCell`実行時の内郚可倉性通垞は犁止されおいる「共有しながら曞き換え」を蚱可する仕組みを組み合わせおこれを解決しおいたす。たた、再垰䞭に `preorder_idx` ずいう「䜕番目を凊理䞭か」のカヌ゜ルを耇数の再垰呌び出しで共有するために `&mut usize`可倉参照を䜿う必芁がありたす。 + +--- + +### 競技プログラミング芖点での分析 + +- `HashMap` で inorder の「倀→むンデックス」を前凊理し、根の䜍眮を O(1) で取埗 +- 配列の実際のコピヌ`Vec` のスラむシングは行わず、むンデックス境界 `[left, right]` だけを枡すこずで䜙分なヒヌプアロケヌションヒヌプ䞊にメモリを確保する操䜜を回避 +- `Rc::new(RefCell::new(...))` はノヌドごずに1回だけのヒヌプアロケヌション + +### 業務開発芖点での分析 + +- `Option>>` の `Option` 郚分が「子ノヌドが存圚しない可胜性」を型で衚珟。`null` チェックのし忘れをコンパむル時に防ぐ +- 入力の長さ䞍䞀臎は `return None` で安党に早期脱出 + +### Rust 特有の考慮点 + +- `preorder_idx` を `&mut usize` ずしお枡すこずで、再垰関数間でカヌ゜ル䜍眮を所有暩を移さずに共有 +- `HashMap` の `.get()` は `Option<&V>` を返すため、`.copied()` で `Option` に倉換しお安党に扱う +- ノヌドぞの曞き蟌みは `.borrow_mut()` 経由で行う`RefCell` の実行時借甚チェック + +> 📖 **このセクションで登堎した甚語** +> +> - **所有暩**: 倀を"誰が管理するか"をコンパむル時に決める Rust 独自の仕組み +> - **`Rc`**: 参照カりントReference Counted。耇数箇所から同じ倀を参照できるスマヌトポむンタ。ただしシングルスレッド専甚 +> - **`RefCell`**: 通垞は犁止されおいる「共有しながら曞き換え」を実行時チェックで蚱可する仕組み内郚可倉性パタヌン +> - **`&mut T`**: 可倉参照。倀の所有暩を移さずに曞き換える暩限を借りる仕組み +> - **ヒヌプアロケヌション**: `Vec` や `Rc` など、動的なメモリ確保操䜜。スタックより䜎速 + +--- + +# 2. アルゎリズムアプロヌチ比范 + +> 💡 同じ問題でも解き方は耇数ありたす。特に Rust では「所有暩の移動が発生するか」「䜙分なアロケヌションが起きるか」が重芁な遞択基準になりたす。 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | Rust実装コスト | 安党性 | 可読性 | 備考 | +| ---------------------------------- | ---------- | ---------- | -------------- | ------ | ------ | ------------------------------------------- | +| **A: 再垰 + Vec スラむシング** | O(n²) | O(n²) | 䜎 | 高 | 高 | ルヌプごずに `Vec::from_slice` でコピヌ発生 | +| **B: 再垰 + HashMap + `&mut idx`** | **O(n)** | O(n) | äž­ | 高 | 高 | ★最適。コピヌなし・O(1)ルックアップ | +| **C: 反埩 + スタック** | O(n) | O(n) | 高 | 高 | 䜎 | `Rc>` の操䜜が耇雑化する | + +> 📖 **このセクションで登堎した甚語** +> +> - **時間蚈算量**: 凊理にかかる手間が入力サむズに察しおどう増えるかの目安 +> - **スラむシング**: 配列・Vec の䞀郚を切り出しおコピヌを䜜る操䜜。O(n) のコストがかかる +> - **ルックアップ**: キヌに察応する倀をデヌタ構造から取り出す操䜜 + +--- + +# 3. 遞択したアルゎリズムず理由 + +- **遞択したアプロヌチ**: **B: 再垰 + HashMap + `&mut usize`** + +## 理由 + +- **A を遞ばなかった理由**: ルヌプのたびに `preorder[1..]` や `inorder[..mid]` で `Vec` のコピヌを生成するず、n 個のノヌドで合蚈 O(n²) のメモリアロケヌションが発生したす。Rust では䞍芁なヒヌプアロケヌションは避けるのが基本蚭蚈方針です +- **C を遞ばなかった理由**: `Rc>` の `.borrow_mut()` を手動スタックで管理するずコヌドが耇雑になり、実行時パニック`borrow_mut` の二重借甚のリスクも高たりたす + +### Rust 特有の最適化ポむント + +- `preorder_idx` を `&mut usize` で枡すこずで、`Rc>` のようなヒヌプアロケヌションを避けおスタック䞊で状態共有ができる +- `HashMap::get().copied()` でれロコスト手曞きの䜎レベルコヌドず同等の速さな倀取埗 +- `Rc::new(RefCell::new(TreeNode::new(val)))` は1ノヌドに぀き1回のアロケヌションのみ + +> 📖 **このセクションで登堎した甚語** +> +> - **れロコスト抜象化**: `.copied()` のような䟿利なメ゜ッドを䜿っおも、手曞きの䜎レベルコヌドず同等の速さになる Rust の特性 +> - **スタックアロケヌション**: `usize` のような固定サむズの倀が関数フレヌム䞊に眮かれる。ヒヌプより高速 +> - **実行時パニック**: コンパむルは通るが、実行䞭に回埩䞍胜な゚ラヌが発生しおプログラムが匷制終了するこず + +--- + +# 4. 実装コヌド + +## コヌドの骚栌先に党䜓像を把握する + +```text +1. 入力怜蚌: preorder ず inorder の長さが䞀臎しなければ None を返す +2. 前凊理: inorder の「倀 → むンデックス」を HashMap に O(n) で登録 +3. preorder カヌ゜ル preorder_idx を 0 で初期化し、&mut で再垰関数に枡す +4. 再垰関数 build(preorder, &mut idx, &map, left, right): + a. left > right なら None を返す郚分朚なし・再垰の終了条件 + b. preorder[*idx] をルヌト倀ずしお取埗、*idx をむンクリメント + c. HashMap でルヌトの inorder 䜍眮を O(1) で取埗 + d. TreeNode を生成し Rc> でラップ + e. .borrow_mut() で巊右の子を再垰的に接続 + f. Some(node) を返す +5. build(0, n-1) を呌び出しお結果を返す +``` + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 3.00 MB +// Beats 18.00% +use std::rc::Rc; +use std::cell::RefCell; +use std::collections::HashMap; + +impl Solution { + pub fn build_tree( + preorder: Vec, + inorder: Vec, + ) -> Option>> { + + // ① 入力怜蚌2぀の配列の長さが䞀臎しない堎合は朚を埩元できない。 + // Rust では panic! より None を返すこずで呌び出し元が安党に凊理できる。 + if preorder.len() != inorder.len() { + return None; + } + + // ② 空の入力ぞの察応ノヌドがなければ朚も存圚しないため None を返す。 + if preorder.is_empty() { + return None; + } + + let n = inorder.len(); + + // ③ 前凊理inorder の「倀 → むンデックス」を HashMap に登録する。 + // なぜか毎回 .iter().position() で O(n) 探玢するず党䜓が O(n²) になる。 + // HashMap に前もっお登録しおおけば .get() が O(1) になり党䜓が O(n) で枈む。 + // 日垞の䟋え図曞通の玢匕カヌド。タむトル倀から棚番号むンデックスを即座に匕ける。 + let mut inorder_map: HashMap = HashMap::with_capacity(n); + for (i, &val) in inorder.iter().enumerate() { + // enumerate() で (むンデックス, 倀ぞの参照) のペアを順に取り出す + inorder_map.insert(val, i); + } + + // ④ preorder カヌ゜ルを初期化する。 + // &mut usize ずしお再垰関数に枡すこずで、耇数の再垰呌び出しが + // 同じカヌ゜ルを「所有暩なし」で共有しお曎新できる。 + // 他蚀語のように static 倉数やクロヌゞャの mutable キャプチャを + // 䜿わなくお枈むため、Rust の所有暩ルヌルず盞性が良い。 + let mut preorder_idx: usize = 0; + + // â‘€ 再垰関数を呌び出しお朚党䜓を構築しお返す + Self::build(&preorder, &mut preorder_idx, &inorder_map, 0, n - 1) + } + + /// inorder の [left, right] 範囲に察応する郚分朚を再垰的に構築する。 + /// + /// # Arguments + /// * `preorder` - 前順探玢の配列借甚・読み取り専甚 + /// * `preorder_idx` - preorder の珟圚䜍眮カヌ゜ル可倉参照で共有 + /// * `inorder_map` - inorder の「倀→むンデックス」マップ借甚 + /// * `left` - 今構築すべき郚分朚の inorder 䞊の巊端むンデックス + /// * `right` - 今構築すべき郚分朚の inorder 䞊の右端むンデックス + /// + /// # Returns + /// `Some(Rc>)` たたは `None`郚分朚なし + /// + /// # Complexity + /// - Time: O(n) ─ 各ノヌドをちょうど1回だけ凊理 + /// - Space: O(n) ─ HashMap + 再垰スタック朚の高さ h 分 + fn build( + preorder: &[i32], + preorder_idx: &mut usize, // &mut usize可倉参照で状態を再垰間共有 + inorder_map: &HashMap, // &HashMap読み取り専甚の借甚 + left: usize, + right: usize, + ) -> Option>> { + + // ⑥ 再垰の終了条件left > right なら郚分朚は空 → None を返す。 + // この `if left > right` チェックは Self::build 内で行うここ。 + // ただし usize は負の数を衚せないため、left=0 か぀ mid=0 のずき + // 呌び出し偎で `mid - 1` を蚈算するずアンダヌフロヌ + // 0未満ぞのラップアラりンドが起きる。 + // よっお䞡方の察応が必芁 + // 1) Self::build 内の `if left > right`ここで空範囲を怜出 + // 2) 呌び出し偎⑩の `mid > left` ガヌドで mid-1 の蚈算自䜓を回避 + if left > right { + return None; + } + + // ⑩ preorder の珟圚䜍眮からルヌト倀を取り出す。 + // preorder は「ルヌト → å·Š → 右」の順なので、 + // 呌ばれた時点の先頭芁玠が必ずこの郚分朚のルヌトになる。 + let root_val = preorder[*preorder_idx]; + *preorder_idx += 1; // 次の再垰のためにカヌ゜ルを進める + + // ⑧ HashMap でルヌト倀が inorder のどこにあるかを O(1) で取埗する。 + // .get() は Option<&usize> を返すので .copied() で Option に倉換。 + // 制玄「preorder ず inorder は同じ朚のもの」が保蚌されおいるため + // .unwrap_or(0) ではなく .expect() でバグを即座に怜出するほうが安党。 + // ただし LeetCode の制玄䞊は必ず存圚するため unwrap で問題ない。 + let mid = *inorder_map.get(&root_val).unwrap(); + + // ⑹ TreeNode を生成し、Rc> でラップする。 + // なぜ Rc か朚の芪が子ノヌドを所有するが、埌で巊右に接続するずきに + // 「䞀時的に耇数箇所から参照したい」ため、参照カりント型が必芁。 + // なぜ RefCell かノヌドを生成した埌に .left / .right を曞き換えるため、 + // 共有した状態でも内郚を倉曎できる「内郚可倉性」が必芁。 + let node = Rc::new(RefCell::new(TreeNode::new(root_val))); + + // ⑩ 巊郚分朚を再垰的に構築する。 + // inorder で「ルヌトの巊偎left 〜 mid-1」が巊郚分朚のノヌド矀。 + // ★ mid == left のずき left..mid-1 は空left > right になるだが + // usize の枛算アンダヌフロヌを防ぐため、mid > left のずきだけ再垰する。 + // mid == left のずきは巊の子なし → None になる。 + node.borrow_mut().left = if mid > left { + Self::build(preorder, preorder_idx, inorder_map, left, mid - 1) + } else { + None // ルヌトが inorder の巊端 → 巊の子は存圚しない + }; + + // ⑪ 右郚分朚を再垰的に構築する。 + // inorder で「ルヌトの右偎mid+1 〜 right」が右郚分朚のノヌド矀。 + // ★ 巊を先に凊理する理由preorder は「ルヌト→巊→右」の順なので + // 巊の再垰が終わるたで右のルヌト倀は preorder に珟れない。 + node.borrow_mut().right = + Self::build(preorder, preorder_idx, inorder_map, mid + 1, right); + + // ⑫ 完成したノヌド巊右の郚分朚が接続枈みを Some でラップしお返す + Some(node) + } +} +``` + +--- + +## 動䜜トレヌス具䜓的な入力䟋 + +```text +入力: preorder = [3, 9, 20, 15, 7] + inorder = [9, 3, 15, 20, 7] + +【前凊理】 inorder_map の構築: + 9→0, 3→1, 15→2, 20→3, 7→4 + +preorder_idx = 0 で build(..., left=0, right=4) を呌び出す + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌱 build(left=0, right=4) + root_val = preorder[0] = 3, *preorder_idx → 1 + mid = inorder_map[3] = 1 + node = Rc> + + ┌── left偎: mid(1) > left(0) → build(left=0, right=0) を呌ぶ + │ root_val = preorder[1] = 9, *preorder_idx → 2 + │ mid = inorder_map[9] = 0 + │ node = Rc> + │ left偎: mid(0) > left(0) は false → None + │ right偎: build(left=1, right=0) → left(1) > right(0) → None + │ return Some(TreeNode(9, left=None, right=None)) + │ + └── right偎: build(left=2, right=4) + root_val = preorder[2] = 20, *preorder_idx → 3 + mid = inorder_map[20] = 3 + node = Rc> + + ┌── left偎: mid(3) > left(2) → build(left=2, right=2) を呌ぶ + │ root_val = preorder[3] = 15, *preorder_idx → 4 + │ mid = inorder_map[15] = 2 + │ left偎: mid(2) > left(2) は false → None + │ right偎: build(left=3, right=2) → left > right → None + │ return Some(TreeNode(15, None, None)) + │ + └── right偎: build(left=4, right=4) + root_val = preorder[4] = 7, *preorder_idx → 5 + mid = inorder_map[7] = 4 + left偎: mid(4) > left(4) は false → None + right偎: build(left=5, right=4) → left > right → None + return Some(TreeNode(7, None, None)) + + return Some(TreeNode(20, left=Some(15), right=Some(7))) + +return Some(TreeNode(3, left=Some(9), right=Some(20))) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +最終結果の朚 + 3 + / \ + 9 20 + / \ + 15 7 +✅ Output: [3, 9, 20, null, null, 15, 7] +``` + +--- + +## `Rc>` を䜿う理由を図で理解する + +```text +【他蚀語TypeScriptでは】 + node.left = buildLeft(); // 普通に代入できる + +【Rustで玠盎に曞こうずするず】 + let node = TreeNode { val, left: None, right: None }; + node.left = build_left(); // ← ゚ラヌ node はすでに move 枈みかもしれない + +【なぜか所有暩の問題】 + ┌──────────────────────────────────────┐ + │ node を䜜る → left を蚭定 → right を蚭定 │ + │ この間ずっず node を「倉曎可胜」で保持 │ + │ か぀「耇数の再垰から参照可胜」にしたい │ + └──────────────────────────────────────┘ + → 「倉曎可胜」か぀「耇数参照可胜」は通垞 Rust では犁止 + +【解決策: Rc>】 + Rc ── 参照カりント耇数箇所から同じ倀を参照できる所有暩の共有 + RefCell ── 内郚可倉性実行時の借甚チェックで、共有しながら曞き換えを蚱可 + + node.borrow_mut().left = Some(child); + ↑ borrow_mut() で「今だけ曞き換えOK」ずいう実行時ロックを取埗 + ロックが取れなければ二重 borrowパニックになるが、 + この問題では垞に䞀぀の堎所からしか borrow しないため安党 +``` + +> 📖 **このセクションで登堎した甚語** +> +> - **`Rc`**: Reference Counted参照カりント型。耇数の堎所から同じデヌタを参照できるが、スレッド間の共有はできない +> - **`RefCell`**: 通垞 Rust が犁止する「共有しながら曞き換え」を実行時チェックで蚱可する型。パフォヌマンスより利䟿性を優先する堎面で䜿う +> - **`.borrow_mut()`**: `RefCell` の内容を曞き換えるための「実行時ロック取埗」メ゜ッド。二重取埗するずパニックが起きる +> - **`usize` アンダヌフロヌ**: `usize` は 0 以䞊の敎数のみ衚せるため、0 - 1 を蚈算するずパニックたたは最倧倀ぞのラップアラりンドが起きる危険がある +> - **`&mut T`可倉参照**: 所有暩を枡さずに「曞き換える暩利だけ」を借りる仕組み。同時に1぀しか存圚できない排他的 diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Typescript.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Typescript.md new file mode 100644 index 0000000..e71f8df --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/Construct_Binary_Tree_from_Preorder_and_Inorder_Traversal_Typescript.md @@ -0,0 +1,340 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: TypeScript +> 適甚ルヌルセット: 共通5ルヌル + TS固有5ルヌル +> 参照ファむル: references/common.md + references/typescript.md + +--- + +# 1. 問題の分析 + +> 💡 **䞀蚀で蚀うず**「2皮類の『朚の探玢順序リスト』を手がかりに、元の二分朚を埩元する問題」です。 + +## なぜ単玔なアプロヌチでは解けないのか + +2぀の配列が䜕を意味するかを理解しないず解けたせん。朚ツリヌの探玢には耇数の「順序」があり、それぞれ異なる情報を持っおいたす。 + +【二分朚binary treeの探玢順序の皮類】 + +```text + 3 + / \ + 9 20 + / \ + 15 7 + +preorder前順= [3, 9, 20, 15, 7] + → ルヌト → å·Š → 右 の順に蚪れる + → ★ 先頭芁玠が必ず「ルヌト」になる + +inorder䞭順= [9, 3, 15, 20, 7] + → å·Š → ルヌト → 右 の順に蚪れる + → ★ ルヌトの「巊右の境界線」が分かる +``` + +この2぀の特性を組み合わせるこずで、元の朚を䞀意に埩元できたす。 + +--- + +### 競技プログラミング芖点での分析 + +- **最優先課題**: `inorder` から根の䜍眮を探す凊理を O(1) に高速化するこず +- 玠盎に実装するず、毎回 `inorder.indexOf(val)` で O(n) の線圢探玢先頭から順に比范する探玢が走り、党䜓 O(n²) になっおしたいたす +- `HashMap`ハッシュマップ蟞曞のように「倀→むンデックス」を瞬時に匕けるデヌタ構造で前凊理するこずで O(n) に改善できたす + +### 業務開発芖点での分析 + +- **型安党性**: `TreeNode | null` ずいう戻り倀の型で「朚がない可胜性null」をコンパむル時TypeScriptがJavaScriptに倉換される段階に衚珟 +- **゚ラヌハンドリング**: 入力配列が空・長さ䞍䞀臎などの異垞系を事前怜蚌 +- **可読性**: 再垰関数自分自身を呌び出す関数の責務を明確に分ける + +### TypeScript特有の考慮点 + +- `Map` で型付きのハッシュマップを定矩できる +- `readonly` 修食子倉曎犁止の印で入力配列の誀倉曎を防止 +- `preorderIndex` を参照ずしお管理するために、クロヌゞャ倖偎の倉数を関数内から参照できる仕組みを掻甚する + +> 📖 **このセクションで登堎した甚語** +> +> - **二分朚binary tree**: 各ノヌドが最倧2぀の子を持぀朚構造デヌタ +> - **preorder前順**: ルヌト → å·Š → 右 の順に探玢する方法 +> - **inorder䞭順**: å·Š → ルヌト → 右 の順に探玢する方法 +> - **HashMapハッシュマップ**: 倀をキヌずしお盎接堎所を指定できる蟞曞のようなデヌタ構造 +> - **コンパむル時**: TypeScriptのコヌドをJavaScriptに倉換する段階 + +--- + +# 2. アルゎリズムアプロヌチ比范 + +> 💡 同じ問題でも解き方は耇数ありたす。それぞれの「速さ時間蚈算量」ず「メモリの䜿い方空間蚈算量」を比范しお、最適なものを遞びたす。 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | TS実装コスト | 型安党性 | 可読性 | 備考 | +| ----------------------- | ---------- | ---------- | ------------ | -------- | ------ | ------------------------ | +| **A: 再垰 + indexOf** | O(n²) | O(n) | 䜎 | 高 | 高 | 毎回線圢探玢するため遅い | +| **B: 再垰 + HashMap** | **O(n)** | O(n) | äž­ | 高 | 高 | ★最適。前凊理で高速化 | +| **C: 反埩スタック** | O(n) | O(n) | 高 | äž­ | 䜎 | 実装が耇雑になる | + +> 💡 **Big-O蚘法の読み方**初孊者向け +> +> - `O(1)`入力サむズに関係なく䞀定時間最速 +> - `O(n)`入力が2倍になるず凊理も玄2倍 +> - `O(n²)`入力が2倍になるず凊理は玄4倍二重ルヌプに倚い +> +> 📖 **このセクションで登堎した甚語** +> +> - **時間蚈算量**: 凊理にかかる手間が入力サむズに察しおどう増えるかの目安 +> - **空間蚈算量**: 凊理䞭に䜿うメモリ量が入力サむズに察しおどう増えるかの目安 +> - **再垰recursion**: 関数が自分自身を呌び出しお問題を解く手法 + +--- + +# 3. 遞択したアルゎリズムず理由 + +- **遞択したアプロヌチ**: **B: 再垰 + HashMapO(n)** + +## 理由 + +- **Aを遞ばなかった理由**: `indexOf` による毎回の線圢探玢先頭から1぀ず぀調べる凊理が O(n) かかり、n 個のノヌドで合蚈 O(n²) になりたす。制玄 `n=3000` なら9,000,000回の比范が必芁になり遅すぎたす +- **Cを遞ばなかった理由**: スタック埌入れ先出しのデヌタ構造を䜿った反埩実装は速床は同じですが、コヌドが耇雑になり保守性埌から倉曎しやすい性質が䞋がりたす + +### TypeScript特有の最適化ポむント + +- `Map` で型安党なハッシュマップを䜜成。`any` 型を䜿わずに枈む +- `preorderIndex` を `{ val: number }` のオブゞェクト参照ずしお持぀こずで、再垰の䞭から安党に状態を共有できるこれをクロヌゞャず呌ぶ +- `readonly number[]` で入力配列の䞍倉性倉曎されないこずをコンパむル時に保蚌 + +> 📖 **このセクションで登堎した甚語** +> +> - **ゞェネリクス**: `Map` の `number` 郚分のように、型を埌から差し蟌める仕組み +> - **クロヌゞャ**: 倖偎のスコヌプにある倉数を、内偎の関数から参照・曎新できる仕組み +> - **保守性**: 将来コヌドを修正・拡匵しやすいかどうかを瀺す性質 + +--- + +# 4. 実装コヌド + +## コヌドの骚栌先に党䜓像を把握する + +```text +1. 入力怜蚌配列長の䞀臎確認・空配列チェック +2. 前凊理inorder の「倀 → むンデックス」HashMap を O(n) で構築 +3. preorder の珟圚䜍眮を指す倉数を初期化 +4. 再垰関数 build(left, right) を定矩 + a. left > right なら null を返す郚分朚が空 + b. preorder[preorderIdx] をルヌトの倀ずしお取埗、むンデックスを進める + c. HashMap でルヌトの inorder 䞊の䜍眮を O(1) で取埗 + d. 巊郚分朚・右郚分朚を再垰的に構築しお接続 +5. build(0, n-1) を呌び出しお朚党䜓を構築しお返す +``` + +```typescript +/** + * preorder ず inorder から二分朚を埩元する + * @param preorder - 前順探玢の配列先頭が垞にルヌト + * @param inorder - 䞭順探玢の配列ルヌトの巊右を分ける境界線 + * @returns 埩元した二分朚のルヌトノヌド空なら null + * @complexity Time: O(n), Space: O(n) + */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // ① 入力怜蚌2぀の配列の長さが䞀臎しない堎合ぱラヌを投げる。 + // 長さが違うず朚を䞀意に定たらないため、凊理を続けおも意味がない。 + if (preorder.length !== inorder.length) { + throw new RangeError( + `preorder length (${preorder.length}) must equal inorder length (${inorder.length})`, + ); + } + + // ② 空の入力ぞの察応ノヌドが1぀もなければ null朚なしを返す。 + if (preorder.length === 0) { + return null; + } + + const n = preorder.length; // 党ノヌド数を保持以降で参照するため + + // ③ 前凊理inorder の「倀 → むンデックス」マップを䜜成する。 + // なぜか毎回 indexOf で探すず O(n) かかるが、 + // HashMap にしおおくず O(1) で䜍眮が分かり、党䜓が O(n) で枈む。 + // 日垞の䟋え図曞通の玢匕カヌドのように、本の名前倀から + // 棚番号むンデックスを即座に匕けるむメヌゞ。 + const inorderIndexMap = new Map(); + for (let i = 0; i < n; i++) { + // inorder[i] の倀をキヌ、䜍眮 i を倀ずしお登録 + inorderIndexMap.set(inorder[i], i); + } + + // ④ preorder を消費しおいく「珟圚䜍眮カヌ゜ル」を初期化する。 + // オブゞェクトにラップするのは、再垰関数の䞭から共有しお + // 曎新できるようにするためクロヌゞャで倀を共有。 + let preorderIdx = 0; + + // â‘€ 再垰関数inorder の [left, right] の範囲に察応する郚分朚を構築する。 + // 「inorder の left〜right の範囲」「今構築すべき郚分朚のノヌド矀」を意味する。 + function build(left: number, right: number): TreeNode | null { + // 範囲が空left > rightなら郚分朚なし → null を返す + // これが再垰の「底終了条件」。ここに達したら折り返す。 + if (left > right) { + return null; + } + + // ⑥ preorder の珟圚䜍眮の倀がこの郚分朚の「ルヌト」になる。 + // preorder は「ルヌト → å·Š → 右」の順なので、 + // 巊郚分朚を凊理し終えるず次のルヌト倀が珟れる。 + const rootVal = preorder[preorderIdx]; + preorderIdx++; // 次の呌び出しのために䜍眮を進める + + // ⑩ HashMap でこのルヌト倀が inorder のどこにあるかを O(1) で取埗する。 + // inorder では「巊ノヌド矀 | ルヌト | 右ノヌド矀」ずいう配眮になるため、 + // この䜍眮を境界ずしお巊右の郚分朚の範囲が決たる。 + const mid = inorderIndexMap.get(rootVal)!; + // `!` は Non-null assertion「これは必ずnullでないず保蚌する」宣蚀。 + // 制玄「preorder ず inorder は同じ朚のもの」が保蚌されおいるため安党に䜿える。 + + // ⑧ TreeNode を生成する。 + // この時点では left/right は未確定次の再垰で決たる。 + const node = new TreeNode(rootVal); + + // ⑹ 巊郚分朚を再垰的に構築する。 + // inorder で「ルヌトの巊偎left 〜 mid-1」が巊郚分朚のノヌド矀。 + // ★ 巊を先に凊理する理由preorder は「ルヌト→巊→右」の順なので + // 巊の再垰が終わるたで右のルヌトは preorder に珟れない。 + node.left = build(left, mid - 1); + + // ⑩ 右郚分朚を再垰的に構築する。 + // inorder で「ルヌトの右偎mid+1 〜 right」が右郚分朚のノヌド矀。 + node.right = build(mid + 1, right); + + // ⑪ 完成したノヌド巊右の郚分朚が接続枈みを返す。 + return node; + } + + // ⑫ inorder 党䜓0 〜 n-1を察象にしお構築開始 + return build(0, n - 1); +} +``` + +--- + +## 動䜜トレヌス具䜓的な入力䟋 + +```text +入力: preorder = [3, 9, 20, 15, 7] + inorder = [9, 3, 15, 20, 7] + +【前凊理】 inorderIndexMap の構築: + 9 → 0, 3 → 1, 15 → 2, 20 → 3, 7 → 4 + +preorderIdx = 0 で build(0, 4) を呌び出す + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌱 build(left=0, right=4) + rootVal = preorder[0] = 3, preorderIdx → 1 + mid = inorderIndexMap.get(3) = 1 + node = TreeNode(3) + + ┌── node.left = build(left=0, right=0) ← inorder[0..0] = [9] + │ rootVal = preorder[1] = 9, preorderIdx → 2 + │ mid = inorderIndexMap.get(9) = 0 + │ node = TreeNode(9) + │ node.left = build(0, -1) → null (left > right なので終了) + │ node.right = build(1, 0) → null (left > right なので終了) + │ return TreeNode(9) ← 巊の子 = 9 が確定 + │ + └── node.right = build(left=2, right=4) ← inorder[2..4] = [15,20,7] + rootVal = preorder[2] = 20, preorderIdx → 3 + mid = inorderIndexMap.get(20) = 3 + node = TreeNode(20) + + ┌── node.left = build(left=2, right=2) ← inorder[2..2] = [15] + │ rootVal = preorder[3] = 15, preorderIdx → 4 + │ mid = inorderIndexMap.get(15) = 2 + │ node = TreeNode(15) + │ node.left = build(2, 1) → null + │ node.right = build(3, 2) → null + │ return TreeNode(15) ← 20 の巊の子 = 15 が確定 + │ + └── node.right = build(left=4, right=4) ← inorder[4..4] = [7] + rootVal = preorder[4] = 7, preorderIdx → 5 + mid = inorderIndexMap.get(7) = 4 + node = TreeNode(7) + node.left = build(4, 3) → null + node.right = build(5, 4) → null + return TreeNode(7) ← 20 の右の子 = 7 が確定 + + return TreeNode(20, left=15, right=7) + + return TreeNode(3, left=9, right=TreeNode(20)) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +最終結果の朚 + 3 + / \ + 9 20 + / \ + 15 7 +✅ Output: [3, 9, 20, null, null, 15, 7] +``` + +--- + +## LeetCode 提出フォヌマット + +```typescript +// Runtime 2 ms +// Beats 93.16% +// Memory 59.97 MB +// Beats 82.11% + +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + if (preorder.length !== inorder.length) { + throw new RangeError( + `preorder length (${preorder.length}) must equal inorder length (${inorder.length})`, + ); + } + if (preorder.length === 0) return null; + + const n = preorder.length; + + // inorder の「倀 → むンデックス」を O(1) で匕けるよう前凊理する + const inorderIndexMap = new Map(); + for (let i = 0; i < n; i++) { + inorderIndexMap.set(inorder[i], i); + } + + // preorder を先頭から順に消費するカヌ゜ル再垰内で共有 + let preorderIdx = 0; + + // inorder の [left, right] 範囲に察応する郚分朚を再垰的に構築する + function build(left: number, right: number): TreeNode | null { + if (left > right) return null; // 範囲が空 → 郚分朚なし + + const rootVal = preorder[preorderIdx++]; // 珟圚のルヌト倀を取埗しお進める + const mid = inorderIndexMap.get(rootVal)!; // ルヌトの inorder 䞊の䜍眮を O(1) で取埗 + + const node = new TreeNode(rootVal); + node.left = build(left, mid - 1); // 巊郚分朚ルヌトの巊偎 + node.right = build(mid + 1, right); // 右郚分朚ルヌトの右偎 + return node; + } + + return build(0, n - 1); +} +``` + +> 📖 **このセクションで登堎した甚語** +> +> - **readonly**: 倉数の倀を倉曎できないようにする TypeScript の修食子。JavaScript にはなく、意図せぬ曞き換えをコンパむル時に防げる +> - **Non-null assertion (`!`)**: 「この倀は null/undefined ではない」ずコンパむラに䌝える TypeScript の構文。確信がある堎合のみ䜿う +> - **TypeError / RangeError**: ゚ラヌの皮類。`TypeError` は型が䞍正な堎合、`RangeError` は倀の範囲が䞍正な堎合に䜿う +> - **再垰recursion**: 関数が自分自身を呌び出しお、問題を小さな郚分問題に分解しお解く手法 +> - **クロヌゞャ**: 倖偎の関数のスコヌプ倉数の有効範囲にある倉数を、内偎の関数から参照・曎新できる仕組み + +--- + +# TypeScript 固有の最適化芳点たずめ + +| 芳点 | 今回の実装での察応 | +| ----------------------------- | ------------------------------------------------------------------------------------------- | +| **型安党性** | `Map` で型付き HashMap、`TreeNode \| null` で null 可胜性を明瀺 | +| **コンパむル時゚ラヌ防止** | `RangeError` で長さ䞍䞀臎を匟き、`!` 䜿甚箇所を制玄で論理的に保蚌 | +| **readonly / むミュヌタブル** | 入力配列を倉曎せず、新しいノヌドを生成しお朚を構築 | +| **型掚論の掻甚** | `inorderIndexMap.get()` の戻り倀が `number \| undefined` であるこずを TypeScript が自動掚論 | +| **Pure function** | 入力配列を倉曎せず、`preorderIdx` のみ内郚で管理副䜜甚なし | diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README.md new file mode 100644 index 0000000..3b12df8 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README.md @@ -0,0 +1,755 @@ +# Construct Binary Tree from Preorder and Inorder Traversal - preorder ず inorder から二分朚を埩元する + +--- + +## 目次Table of Contents + +- [抂芁](#overview) +- [アルゎリズム芁点 TL;DR](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [蚈算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポむント](#cpython) +- [゚ッゞケヌスず怜蚌芳点](#edgecases) +- [FAQ](#faq) + +--- + +

抂芁

+ +> 💡 **䞀蚀で蚀うず**「2皮類の"朚の探玢順リスト"を手がかりに、元の二分朚を Python のクラスむンスタンスずしお埩元する問題」です。 + +### この問題が難しい理由 + +2぀の配列が枡されたすが、**どちらか䞀方だけでは朚を䞀意に埩元できたせん**。たずえば preorder前順探玢ルヌト → å·Š → 右 の順で蚪れるだけでは、ルヌトは分かっおも巊右の分割点が特定できたせん。inorder䞭順探玢巊 → ルヌト → 右 の順で蚪れるず組み合わせおはじめお「ルヌトの巊に䜕ノヌドあるか」が確定し、朚を䞀意に埩元できたす。 + +### 問題の芁件 + +| 項目 | 内容 | +| ---- | ------------------------------------------------------------------- | +| 入力 | `preorder: List[int]`前順配列・`inorder: List[int]`䞭順配列 | +| 出力 | `Optional[TreeNode]`埩元した朚のルヌトノヌド | +| 制玄 | `1 ≀ n ≀ 3000`、倀はすべおナニヌク、`-3000 ≀ preorder[i] ≀ 3000` | +| 保蚌 | preorder ず inorder は必ず同じ朚の探玢結果 | + +> 📖 **この章で登堎した甚語** +> +> - **preorder前順探玢**ルヌト → 巊郚分朚 → 右郚分朚 の順にノヌドを蚪れる探玢。先頭芁玠が必ずルヌトになる +> - **inorder䞭順探玢**巊郚分朚 → ルヌト → 右郚分朚 の順にノヌドを蚪れる探玢。ルヌトの巊右の境界線が分かる +> - **TreeNode**二分朚の1぀のノヌドを衚すクラス。`val`倀・`left`巊の子・`right`右の子を持぀ +> - **Optional[X]**`X` たたは `None` のどちらかであるこずを衚す型ヒント + +--- + +

アルゎリズム芁点TL;DR

+ +> 💡 **TL;DR**Too Long; Didn't Readずは「長くお読めない人向けの芁玄」ずいう意味です。ここでは「なんずなくこういう手順で解くんだな」ずいうむメヌゞを掎む章ずしお読んでください。 + +- **戊略**「preorder の先頭 = ルヌト」ずいう性質ず「inorder のルヌト䜍眮 = 巊右の境界線」ずいう性質を再垰的に利甚しお朚を埩元する +- **前凊理**`inorder` の「倀 → むンデックス」を `dict`ハッシュマップに登録しおおく。なぜか毎回 `list.index()` で線圢探玢するず党䜓が O(n²) になるが、`dict` なら O(1) で䜍眮を取り出せる +- **カヌ゜ル共有**`preorder` を先頭から順に消費するカヌ゜ル倉数を `nonlocal` で再垰関数間で共有する。なぜか各再垰呌び出しが「今どのルヌトを凊理するか」を順番通りに取り出す必芁があるため +- **時間蚈算量**O(n)各ノヌドをちょうど1回だけ凊理 +- **空間蚈算量**O(n)`dict` の n ゚ントリ + 再垰スタックの高さ h 分 + +```text +【2぀の配列が持぀情報の敎理】 + +preorder = [3, 9, 20, 15, 7] + ↑ + 先頭が必ずルヌト + +inorder = [9, 3, 15, 20, 7] + ↑ + ルヌト「3」の䜍眮が分かる + 巊偎 [9] = 巊郚分朚 + 右偎 [15,20,7] = 右郚分朚 +``` + +> 📖 **この章で登堎した甚語** +> +> - **再垰recursion**関数が自分自身を呌び出しお、問題を小さな郚分問題に分解しお解く手法 +> - **`dict`ハッシュマップ**キヌから倀を平均 O(1) で取り出せる Python の組み蟌みデヌタ構造。内郚はハッシュテヌブル倀の堎所を蚈算で盎接求める仕組み +> - **`nonlocal`**内偎の関数から倖偎でも `global` ではないスコヌプにある倉数を曞き換えるための Python キヌワヌド +> - **O(n²)**入力が2倍になるず凊理が玄4倍になるこず。二重ルヌプや毎回の線圢探玢に倚い + +--- + +

図解

+ +> 💡 **Mermaid フロヌチャヌトの読み方**ひし圢`{}`は条件分岐Yes/No で凊理が分かれる、長方圢`[]`は凊理ステップを衚したす。矢印はデヌタや凊理の流れを瀺したす。䞊から䞋ぞ順に読み進めおください。 + +### フロヌチャヌト + +この図は `buildTree` 関数党䜓の凊理の流れを衚しおいたす。前凊理フェヌズで `dict` を構築し、再垰フェヌズで朚を組み立おる2段構造になっおいたす。 + +```mermaid +flowchart TD + Start[Start buildTree] --> LenCheck{lengths equal} + LenCheck -- No --> RetNone1[Return None] + LenCheck -- Yes --> EmptyCheck{preorder empty} + EmptyCheck -- Yes --> RetNone2[Return None] + EmptyCheck -- No --> BuildMap[Build inorder dict value to index] + BuildMap --> InitIdx[Init preorder_idx = 0] + InitIdx --> CallBuild[Call build lo=0 hi=n-1] + CallBuild --> RecBase{lo > hi} + RecBase -- Yes --> RetNone3[Return None] + RecBase -- No --> GetRoot[root_val = preorder at idx, idx plus 1] + GetRoot --> GetMid[mid = inorder_dict root_val] + GetMid --> MakeNode[node = TreeNode root_val] + MakeNode --> RecLeft[node.left = build lo mid-1] + RecLeft --> RecRight[node.right = build mid+1 hi] + RecRight --> RetNode[Return node] +``` + +**各ノヌドの意味** + +- `Start[Start buildTree]`関数の入り口。`preorder` ず `inorder` を受け取る +- `LenCheck{lengths equal}`2぀の配列の長さが䞀臎するかを刀定する条件分岐 +- `BuildMap[Build inorder dict...]``inorder` の「倀→むンデックス」を `dict` に登録する前凊理ステップ +- `InitIdx[Init preorder_idx = 0]`preorder を先頭から消費するカヌ゜ルを初期化 +- `RecBase{lo > hi}`再垰の終了条件。範囲が空lo > hiなら郚分朚なし +- `GetRoot[root_val = ...]`preorder の珟圚䜍眮からルヌト倀を取り出し、カヌ゜ルを進める +- `GetMid[mid = ...]``dict` でルヌトの inorder 䞊の䜍眮を O(1) で取埗 +- `RecLeft / RecRight`巊右の郚分朚を再垰的に構築しお接続 + +--- + +### デヌタフロヌ図 + +この図は入力の2配列からノヌドが生成され、朚ずしお組み立おられるたでのデヌタの倉換を衚しおいたす。 + +```mermaid +graph LR + subgraph Precheck + A[preorder array] --> V[Validate lengths] + B[inorder array] --> V + end + subgraph Preprocessing + V --> D[Build inorder_dict] + D --> E[key: value, val: index] + end + subgraph Recursion + E --> F[build lo hi] + F --> G[TreeNode root_val] + G --> H[node.left recursive] + G --> I[node.right recursive] + end + H --> J[Final Tree Root] + I --> J +``` + +**䞻芁な流れの説明** + +- `preorder` / `inorder` → `Validate`長さ䞍䞀臎を早期怜出 +- `Build inorder_dict`O(n) の前凊理で「倀→むンデックス」を登録 +- `build lo hi` → `TreeNode`再垰的にノヌドを生成しお芪子関係を接続 +- 2぀の `recursive` → `Final Tree Root`巊右郚分朚がルヌトに接続されお完成 + +--- + +### 代衚䟋でのトレヌス + +入力 `preorder=[3,9,20,15,7]`・`inorder=[9,3,15,20,7]` を䜿っお各ステップを远いたす。 + +```text +【前凊理】inorder_dict の構築O(n): + { 9:0, 3:1, 15:2, 20:3, 7:4 } + +preorder_idx = 0 で build(lo=0, hi=4) を呌び出す +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Step 1: build(lo=0, hi=4) + lo(0) ≀ hi(4) → 続行 + root_val = preorder[0] = 3, preorder_idx → 1 + mid = inorder_dict[3] = 1 + node = TreeNode(3) + + Step 2: node.left = build(lo=0, hi=0) ← inorder[0..0]=[9] + root_val = preorder[1] = 9, preorder_idx → 2 + mid = inorder_dict[9] = 0 + node = TreeNode(9) + node.left = build(lo=0, hi=-1) → lo>hi → None + node.right = build(lo=1, hi=0) → lo>hi → None + ✅ return TreeNode(9) + + Step 3: node.right = build(lo=2, hi=4) ← inorder[2..4]=[15,20,7] + root_val = preorder[2] = 20, preorder_idx → 3 + mid = inorder_dict[20] = 3 + node = TreeNode(20) + + Step 4: node.left = build(lo=2, hi=2) ← inorder[2..2]=[15] + root_val = preorder[3] = 15, preorder_idx → 4 + mid = inorder_dict[15] = 2 + node = TreeNode(15) + node.left = build(lo=2, hi=1) → lo>hi → None + node.right = build(lo=3, hi=2) → lo>hi → None + ✅ return TreeNode(15) + + Step 5: node.right = build(lo=4, hi=4) ← inorder[4..4]=[7] + root_val = preorder[4] = 7, preorder_idx → 5 + mid = inorder_dict[7] = 4 + node = TreeNode(7) + node.left = build(lo=4, hi=3) → lo>hi → None + node.right = build(lo=5, hi=4) → lo>hi → None + ✅ return TreeNode(7) + + ✅ return TreeNode(20, left=TreeNode(15), right=TreeNode(7)) + +✅ return TreeNode(3, left=TreeNode(9), right=TreeNode(20,...)) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +最終結果の朚 + 3 + / \ + 9 20 + / \ + 15 7 +Output: [3,9,20,null,null,15,7] ✅ +``` + +> 📖 **この章で登堎した甚語** +> +> - **フロヌチャヌト**凊理の手順を図圢ず矢印で衚したもの。ひし圢条件分岐、長方圢凊理ステップ +> - **デヌタフロヌ図**デヌタがどのように倉換・移動するかを瀺す図 +> - **前凊理Preprocessing**メむンの凊理を始める前に行う準備䜜業。この問題では `dict` の構築がそれにあたる + +--- + +

正しさのスケッチ

+ +> 💡 「正しさのスケッチ」ずは、アルゎリズムが**垞に正しい答えを返すこずの根拠**を敎理したものです。数孊的な厳密蚌明ではなく「なぜ正しいず蚀えるか」の説明です。 + +### 䞍倉条件アルゎリズムが正しく動くために、凊理䞭ずっず成り立ち続けるべき条件 + +`build(lo, hi)` が呌ばれるずき、以䞋の2぀が垞に成り立ちたす + +1. **`preorder[preorder_idx]` はこの郚分朚のルヌト倀である** + preorder は「ルヌト→巊→右」の順なので、巊の再垰が終わるたびに次のルヌト倀が `preorder_idx` の䜍眮に来たす。巊を先に凊理するこずでこの順序が厩れたせん。 + +2. **`inorder[lo..hi]` はこの郚分朚のノヌド党䜓を衚す** + `mid` でルヌトを特定したあず、巊郚分朚は `inorder[lo..mid-1]`、右郚分朚は `inorder[mid+1..hi]` の範囲に完党に含たれたす。 + +### 基底条件再垰の終了条件。これがないず無限ルヌプになる + +`lo > hi` のずき `None` を返したす。これは「察象ずなるノヌドが存圚しない空の郚分朚」を意味し、盎感的にも正しい「朚のない堎所は None である」です。 +䟋葉ノヌド `TreeNode(9)` の巊子を求めるずき `build(lo=0, hi=-1)` が呌ばれ、`lo(0) > hi(-1)` で即座に `None` が返りたす。 + +### 網矅性すべおのケヌスをもれなく凊理できおいるずいう保蚌 + +- `preorder` の各芁玠は `preorder_idx` の単調増加によっおちょうど1回だけ消費されたす +- `inorder_dict` は事前にすべおの倀を登録しおいるため、`inorder_dict[root_val]` は必ず成功したす制玄「preorder ず inorder は同じ朚のもの」より + +### 終了性アルゎリズムが必ず有限ステップで終わるずいう保蚌 + +各再垰呌び出しで凊理察象の範囲 `(hi - lo + 1)` は必ず1以䞊瞮小したす巊再垰は `mid-1`、右再垰は `mid+1` を枡すため。したがっお有限回の呌び出しで必ず `lo > hi` に到達しお終了したす。 + +> 📖 **この章で登堎した甚語** +> +> - **䞍倉条件**アルゎリズムが正しく動くために、凊理䞭ずっず成り立ち続けるべき条件 +> - **基底条件**再垰の終了条件。これがないず無限ルヌプスタックオヌバヌフロヌになる +> - **終了性**アルゎリズムが必ず有限ステップで終わるずいう保蚌 +> - **網矅性**すべおのケヌスをもれなく凊理できおいるずいう保蚌 + +--- + +

蚈算量

+ +> 💡 蚈算量ずは「入力が倧きくなるに぀れお、凊理にかかる時間・メモリがどう増えるか」の目安です。 + +| 蚘法 | 意味 | 盎感的なむメヌゞ | +| ------------ | ---------------------- | --------------------------- | +| `O(1)` | 入力サむズによらず䞀定 | 蟞曞で盎接ペヌゞを開く | +| `O(n)` | 入力に比䟋しお増加 | リストを端から順に読む | +| `O(n log n)` | n よりやや速く増加 | 蟞曞を二分探玢で匕く × n 回 | +| `O(n²)` | 入力の2乗で増加 | 党ペアを総圓たりで確認する | + +### 時間蚈算量O(n) + +| 凊理 | 蚈算量 | 理由 | +| ---------------------------------- | -------- | ------------------------------- | +| `inorder_dict` の構築 | O(n) | 党芁玠を1回ず぀登録 | +| `build` の再垰呌び出し合蚈 | O(n) | 各ノヌドをちょうど1回だけ凊理 | +| `inorder_dict[root_val]` 1回あたり | O(1) | `dict` の平均ルックアップコスト | +| **合蚈** | **O(n)** | | + +### 空間蚈算量O(n) + +| 䜿甚メモリ | 蚈算量 | 理由 | +| ------------------ | -------- | ---------------------------------------------------- | +| `inorder_dict` | O(n) | n 個のキヌ・倀ペアを保持 | +| 再垰スタック | O(h) | h は朚の高さ。バランス朚は O(log n)、偏った朚は O(n) | +| 生成ノヌド出力 | O(n) | 埩元した朚党䜓のノヌド数 | +| **合蚈** | **O(n)** | | + +### アプロヌチ別比范 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | 備考 | +| ------------------------ | ---------- | ---------- | ----------------------------- | +| `dict` 前凊理 + 再垰 | O(n) | O(n) | ★採甚。最速・コヌド明瞭 | +| `list.index()` + 再垰 | O(n²) | O(n) | n=3000 で玄9M操䜜、TLE リスク | +| 反埩スタック+ `dict` | O(n) | O(n) | 同速だが実装が耇雑 | + +> 📖 **この章で登堎した甚語** +> +> - **時間蚈算量**入力の倧きさに察しお凊理にかかる手間がどう増えるかの目安 +> - **空間蚈算量**凊理䞭に䜿うメモリ量がどう増えるかの目安 +> - **ルックアップコスト**`dict` でキヌに察応する倀を取り出すのにかかるコスト。平均 O(1) +> - **再垰スタック**再垰呌び出しのたびに関数の状態が積たれるメモリ領域。深さが朚の高さに比䟋する + +--- + +

Python 実装

+ +> 💡 コヌドを読む前に、実装の骚栌を確認したしょう。 + +```text +実装の骚栌 +1. sys.setrecursionlimit で再垰深床の䞊限を緩和する最悪 3000 段の再垰に備えるため +2. 入力怜蚌型チェック・長さ䞍䞀臎・空リストを早期怜出する +3. 前凊理inorder の「倀→むンデックス」を dict 内包衚蚘で O(n) 構築 +4. preorder カヌ゜ル倉数を初期化し、nonlocal で再垰間共有する準備 +5. 再垰関数 build(lo, hi) + a. lo > hi なら None を返す再垰の終了条件 + b. preorder[preorder_idx] をルヌト倀ずしお取埗し、カヌ゜ルを進める + c. dict でルヌトの inorder 䞊の䜍眮を O(1) で取埗 + d. TreeNode を生成し、巊右を再垰的に構築しお接続しお返す +6. build(0, n-1) を呌び出しお結果を返す +``` + +### 業務開発版型安党・pylance 察応・゚ラヌハンドリング重芖 + +【業務開発版を䜿う堎面】 +チヌムで長期間メンテナンスするプロダクションコヌドに向きたす。゚ラヌの原因が分かりやすく、埌から読んだ人が意図を理解できる構造になっおいたす。pylance による静的型チェックも通りたす。 + +```python +from __future__ import annotations +# 型ヒントの前方参照を有効にする。 +# 䟋TreeNode が自分自身を型ヒントに䜿える`left: Optional[TreeNode]`。 + +import sys +from typing import Optional, TYPE_CHECKING + +# 再垰深床の䞊限を緩和する。 +# Python のデフォルトは 1000 だが、本問は最悪 3000 段完党に偏った朚になりうる。 +# モゞュヌルロヌド時に1回だけ実行されるよう、クラスの倖に配眮しおいる。 +sys.setrecursionlimit(10_000) + +# TYPE_CHECKING ブロックpylance静的型チェッカヌ向けに TreeNode の型情報を提䟛する。 +# 実行時ではなく型チェック時にのみ読み蟌たれるため、LeetCode の実行環境でも安党。 +if TYPE_CHECKING: + class TreeNode: + val: int + left: Optional[TreeNode] + right: Optional[TreeNode] + def __init__( + self, + val: int = 0, + left: Optional[TreeNode] = None, + right: Optional[TreeNode] = None, + ) -> None: ... + +# Provide a lightweight TreeNode fallback for environments that do not supply it. +class TreeNode: + __slots__ = ("val", "left", "right") + def __init__( + self, + val: int = 0, + left: Optional["TreeNode"] = None, + right: Optional["TreeNode"] = None, + ) -> None: + self.val = val + self.left = left + self.right = right + + +class Solution: + def buildTree( + self, + preorder: list[int], + inorder: list[int], + ) -> Optional[TreeNode]: + """ + preorder前順ず inorder䞭順から二分朚を埩元する。 + + Args: + preorder: 前順探玢の配列先頭が必ずルヌト + inorder: 䞭順探玢の配列ルヌトの巊右を区切る境界線 + + Returns: + 埩元した二分朚のルヌトノヌド。空の堎合は None。 + + Raises: + TypeError: 匕数がリストでない堎合 + ValueError: 2぀の配列の長さが異なる堎合 + + Complexity: + Time: O(n) - 各ノヌドをちょうど1回だけ凊理 + Space: O(n) - dictn ゚ントリ+ 再垰スタック高さ h + """ + # ① 型チェックlist 以倖が枡された堎合に分かりやすい゚ラヌを出す。 + # Python は動的型付けなので実行時たで型゚ラヌに気づかない。 + # isinstance() で早期怜出するこずでデバッグを容易にする。 + if not isinstance(preorder, list) or not isinstance(inorder, list): + raise TypeError("Both preorder and inorder must be lists") + + # ② 長さ䞍䞀臎チェック2぀の配列が同じ朚を衚しおいない堎合は埩元䞍可。 + if len(preorder) != len(inorder): + raise ValueError( + f"Length mismatch: preorder={len(preorder)}, inorder={len(inorder)}" + ) + + # ③ 空リストチェックノヌドが1぀もなければ朚なし → None を返す。 + if not preorder: + return None + + n = len(inorder) + + # ④ 前凊理inorder の「倀 → むンデックス」を dict 内包衚蚘で O(n) 構築。 + # なぜ dict かlist.index() は C 実装でも O(n) の線圢探玢のため、 + # n ノヌドで合蚈 O(n²) になる。dict.__getitem__ は平均 O(1)。 + # 日垞の䟋え図曞通の玢匕カヌド。本のタむトル倀から棚番号むンデックスを即座に匕ける。 + inorder_index: dict[int, int] = {val: i for i, val in enumerate(inorder)} + + # â‘€ preorder を先頭から消費するカヌ゜ルを初期化する。 + # int は Python のむミュヌタブル倉曎䞍可型なので、 + # nonlocal を䜿っお倖偎スコヌプの倉数を盎接曞き換える方匏をずる。 + preorder_idx: int = 0 + + def build(lo: int, hi: int) -> Optional[TreeNode]: + """ + inorder の [lo, hi] 範囲に察応する郚分朚を再垰的に構築する。 + + Args: + lo: この郚分朚の inorder 䞊の巊端むンデックス + hi: この郚分朚の inorder 䞊の右端むンデックス + """ + # nonlocal 宣蚀倖偎の preorder_idx を曞き換えるこずをPythonに䌝える。 + # これがないず、代入時に新しいロヌカル倉数ずしお扱われ倖偎が曎新されないバグが起きる。 + nonlocal preorder_idx + + # ⑥ 再垰の終了条件範囲が空lo > hiなら郚分朚なし → None を返す。 + # 䟋葉ノヌドの巊子を求めるずき lo=0, hi=-1 でここに到達する。 + if lo > hi: + return None + + # ⑩ preorder の珟圚䜍眮の倀がこの郚分朚のルヌトになる。 + # preorder は「ルヌト→巊→右」の順なので、 + # 呌ばれた時点の先頭が必ずこの郚分朚のルヌト倀になる。 + root_val: int = preorder[preorder_idx] + preorder_idx += 1 # 次の再垰のためにカヌ゜ルを進める + + # ⑧ dict でルヌト倀の inorder 䞊の䜍眮を O(1) で取埗する。 + # この䜍眮midを境に「巊偎 = 巊郚分朚」「右偎 = 右郚分朚」が決たる。 + # 制玄「preorder ず inorder は同じ朚のもの」が保蚌されおいるため + # KeyError は発生しない。 + mid: int = inorder_index[root_val] + + # ⑹ TreeNode を生成し、巊右を再垰的に構築しお接続する。 + # ★ 巊を先に構築する理由preorder は「ルヌト→巊→右」の順なので + # 巊の再垰が終わるたで右のルヌト倀は preorder に珟れない。 + node = TreeNode(root_val) + node.left = build(lo, mid - 1) # 巊郚分朚mid の巊偎 + node.right = build(mid + 1, hi) # 右郚分朚mid の右偎 + return node + + # ⑩ inorder 党䜓0 〜 n-1を察象ずしお朚党䜓を構築しお返す + return build(0, n - 1) +``` + +--- + +### 競技プログラミング版速床・コヌドの短さ優先 + +【競技プログラミング版を䜿う堎面】 +LeetCode の制限時間内に通すこずが目的のコヌドです。型チェックや゚ラヌハンドリングを省略し、最小限のコヌドで最速実行を目指したす。 + +```python +import sys +from typing import Optional + +sys.setrecursionlimit(10_000) # 偏った朚での深い再垰に備えお䞊限を緩和 + + +class Solution: + def buildTree( + self, preorder: list[int], inorder: list[int] + ) -> Optional[TreeNode]: + # dict 内包衚蚘で inorder の「倀→むンデックス」を前凊理O(n) + idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)} + pre_i = 0 # preorder のカヌ゜ル + + def build(lo: int, hi: int) -> Optional[TreeNode]: + nonlocal pre_i + if lo > hi: + return None + val = preorder[pre_i] + pre_i += 1 + mid = idx_map[val] + node = TreeNode(val) + node.left = build(lo, mid - 1) + node.right = build(mid + 1, hi) + return node + + return build(0, len(inorder) - 1) +``` + +--- + +### 動䜜トレヌス業務版コヌドの䞻芁ステップ確認 + +```text +入力: preorder=[3,9,20,15,7], inorder=[9,3,15,20,7] + +Step 1: isinstance チェック → 䞡方 list ✅ +Step 2: 長さチェック → len=5, len=5 → 䞀臎 ✅ +Step 3: 空チェック → preorder は空でない ✅ +Step 4: inorder_index = {9:0, 3:1, 15:2, 20:3, 7:4} (dict 内包衚蚘 O(n)) +Step 5: preorder_idx = 0 +Step 6: build(lo=0, hi=4) を呌び出す + + build(0,4): root_val=3, preorder_idx→1, mid=1 + node = TreeNode(3) + node.left = build(0, 0) → root_val=9, preorder_idx→2, mid=0 + node = TreeNode(9) + node.left = build(0,-1) → lo>hi → None + node.right = build(1, 0) → lo>hi → None + ✅ return TreeNode(9) + node.right = build(2, 4) → root_val=20, preorder_idx→3, mid=3 + node = TreeNode(20) + node.left = build(2, 2) → root_val=15, mid=2 → TreeNode(15, None, None) + node.right = build(4, 4) → root_val=7, mid=4 → TreeNode(7, None, None) + ✅ return TreeNode(20) + ✅ return TreeNode(3, left=9, right=20) + +最終結果: [3, 9, 20, null, null, 15, 7] ✅ +``` + +> 📖 **この章で登堎した甚語** +> +> - **`nonlocal`**内偎の関数から倖偎のロヌカル倉数を曞き換えるための Python キヌワヌド。`global`モゞュヌル倉数ずは異なる +> - **dict 内包衚蚘**`{k: v for k, v in iterable}` ずいう圢で dict を1行で䜜る Python 固有の曞き方 +> - **`TYPE_CHECKING`**型チェッカヌpylanceが実行するずきだけ `True` になるフラグ。実行時コストれロで型情報を提䟛できる +> - **`__slots__`**クラスが持おる属性を明瀺的に制限するこずでメモリを節玄する Python の仕組み + +--- + +

CPython 最適化ポむント

+ +> 💡 この章では「同じ凊理でも Python の曞き方によっお速さが倉わる理由」を説明したす。**最適化前のコヌド → 最適化埌のコヌド → なぜ速くなるか** の3点セットで確認したしょう。 + +### ポむント1`list.index()` ではなく `dict` を䜿う + +```python +# ❌ 最適化前毎回 list.index() で線圢探玢O(n) × n ノヌド = O(n²) 党䜓 +# list.index() はC実装で高速だが、それでも先頭から順に比范する O(n) 操䜜 +def build_slow(lo, hi): + root_val = preorder[preorder_idx] + mid = inorder.index(root_val) # ← ここが毎回 O(n) + ... + +# ✅ 最適化埌dict で前凊理しお O(1) ルックアップ +inorder_index = {val: i for i, val in enumerate(inorder)} # O(n) の前凊理1回だけ + +def build_fast(lo, hi): + root_val = preorder[preorder_idx] + mid = inorder_index[root_val] # ← O(1) のハッシュルックアップ + ... + +# なぜ速いかdict.__getitem__ は Python 内郚でハッシュ倀を蚈算しお盎接䜍眮を求めるため +# リストのように先頭から順に比范する必芁がない。 +# n=3000 のずきlist.index 版 ≈ 4,500,000 回比范 → dict 版 ≈ 3,000 回ルックアップ +``` + +### ポむント2dict 内包衚蚘でリスト生成コストを削枛 + +```python +# ❌ 最適化前for ルヌプず dict.update() の組み合わせ玔 Python ルヌプ +inorder_index = {} +for i in range(len(inorder)): + inorder_index[inorder[i]] = i # Python むンタヌプリタを経由する for ルヌプ + +# ✅ 最適化埌dict 内包衚蚘バむトコヌドレベルで最適化された専甚呜什を䜿う +inorder_index = {val: i for i, val in enumerate(inorder)} + +# なぜ速いかPython のリスト/dict 内包衚蚘は CPython のバむトコヌドレベルで +# LIST_APPEND / MAP_ADD ずいう専甚の高速呜什に倉換される。 +# 通垞の for ルヌプに比べおむンタヌプリタのオヌバヌヘッドが少ない。 +``` + +### ポむント3`sys.setrecursionlimit` で再垰䞊限を緩和 + +```python +import sys + +# ❌ 蚭定なしPython デフォルトの再垰深床は 1000 +# 完党に偏った朚n=3000では深さ 3000 の再垰が起き RecursionError になる + +# ✅ 蚭定あり事前に䞊限を緩和しおおく +sys.setrecursionlimit(10_000) # クラスの倖・ファむルの先頭近くに曞く + +# なぜ 10_000 かn ≀ 3000 なので 3000 段の再垰に察しお䜙裕を持たせた倀。 +# 倧きすぎるずスタックメモリを倧量消費するため、必芁最小限にずどめる。 +``` + +### ポむント4`nonlocal` vs `リストラップ` の䜿い分け + +```python +# ① nonlocal を䜿う方法今回採甚・可読性が高い +preorder_idx = 0 +def build(lo, hi): + nonlocal preorder_idx + preorder_idx += 1 # 倖偎の倉数を盎接曞き換える + +# ② リストラップを䜿う方法nonlocal が䜿えない堎合の代替 +preorder_idx = [0] # リストに入れるず参照枡しになる +def build(lo, hi): + preorder_idx[0] += 1 # リストの䞭身を曞き換える所有暩は倉わらない + +# なぜ nonlocal を掚奚するか +# Python の int は䞍倉型immutableのため、+=再代入は新しいオブゞェクトを䜜る。 +# nonlocal なしで += するず「新しいロヌカル倉数ぞの代入」ずしお扱われ倖偎が曎新されない。 +# nonlocal を明瀺するこずで、読み手に「倖偎の倉数を意図的に曞き換えおいる」ず䌝えられる。 +``` + +> 📖 **この章で登堎した甚語** +> +> - **バむトコヌド**Python の゜ヌスコヌドが CPython によっお倉換される䞭間衚珟。実際にはこの圢匏で実行される +> - `dict.__getitem__``dict[key]` ずいう操䜜の内郚実装。ハッシュ蚈算で盎接䜍眮を求めるため O(1) +> - **むンタヌプリタオヌバヌヘッド**Python コヌドの各呜什を解釈・実行するためにかかるコスト。C 実装の組み蟌み関数はこれを倧幅に削枛できる +> - **䞍倉型immutable**`int`・`str`・`tuple` など、生成埌に倀を倉曎できない型。`+=` は新しいオブゞェクトを䜜る再代入になる + +--- + +

゚ッゞケヌスず怜蚌芳点

+ +> 💡 ゚ッゞケヌスずは「空リスト・単䞀ノヌド・極端な圢の朚」など通垞ずは異なる境界的な入力のこずです。゚ッゞケヌスを芋萜ずすず、普通のテストは通るのに特定の入力でだけバグが発生したす。 + +| # | ケヌス名 | 入力 | 期埅出力 | なぜ問題になりうるか | +| --- | -------------------- | ---------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | 単䞀ノヌド | `pre=[-1]`, `ino=[-1]` | `TreeNode(-1)` | `build(0,0)` で `lo==hi` になり終了条件ギリギリ。`lo-1=-1` で `lo>hi` になるかチェック | +| 2 | 右に偏った朚 | `pre=[1,2,3]`, `ino=[1,2,3]` | 1→None→2→None→3 | mid が垞に lo ず䞀臎するため巊再垰が `build(lo,lo-1)` になる。`build(lo, hi)` の終了条件 `lo > hi` が正しく機胜するかを確認 | +| 3 | 巊に偏った朚 | `pre=[3,2,1]`, `ino=[1,2,3]` | 3→2→1→None 右偎党郚 None | mid が垞に hi ず䞀臎するため右再垰が `build(hi+1,hi)` = `build(x+1,x)` になる。終了条件が正しく機胜するかを確認 | +| 4 | 負の倀を含む | `pre=[-3,9,-20]`, `ino=[9,-3,-20]` | `TreeNode(-3, TreeNode(9), TreeNode(-20))` | `dict` のキヌに負の倀が䜿えるか確認。Python の `dict` は任意の `int` をキヌにできるため問題ない | +| 5 | 最倧サむズ | n=3000 の完党二分朚 | 正垞に朚を返す | 再垰深床が玄 log₂(3000) ≈ 12 段に収たる。`sys.setrecursionlimit` なしでも動くが念のため蚭定枈み | +| 6 | 最倧サむズ偏り朚 | n=3000 の䞀列の朚 | 正垞に朚を返す | 再垰深床が 3000 段になる。`sys.setrecursionlimit` なしでは `RecursionError` が発生する | +| 7 | 業務版長さ䞍䞀臎 | `pre=[1,2]`, `ino=[1]` | `ValueError` | バリデヌションが正しく機胜するかを確認 | +| 8 | 業務版型゚ラヌ | `pre="abc"`, `ino=[1]` | `TypeError` | `isinstance` チェックが機胜するかを確認 | + +```python +# ゚ッゞケヌス確認甚のサンプル呌び出しテストコヌドではなく動䜜確認の参考 + +s = Solution() + +# ケヌス1単䞀ノヌド +assert s.buildTree([-1], [-1]).val == -1 # type: ignore[union-attr] + +# ケヌス2右に偏った朚 +root = s.buildTree([1, 2, 3], [1, 2, 3]) +assert root is not None and root.val == 1 +assert root.left is None +assert root.right is not None and root.right.val == 2 + +# ケヌス7業務版の ValueError +import sys +try: + s.buildTree([1, 2], [1]) + assert False, "Should have raised ValueError" +except ValueError: + pass # 期埅通り +``` + +> 📖 **この章で登堎した甚語** +> +> - **゚ッゞケヌス**空のリスト・芁玠1぀・最倧サむズ入力など、境界的な条件の入力 +> - **境界倀**制玄の䞊限・䞋限にあたる倀。本問では n=1最小ず n=3000最倧 +> - **偏った朚Skewed Tree**すべおのノヌドが巊だけ・たたは右だけに぀ながった朚。二分探玢朚の最悪ケヌスで、再垰深床が n になる +> - **RecursionError**Python の再垰深床制限を超えたずきに発生する゚ラヌ + +--- + +

FAQ

+ +> 💡 FAQは「初孊者が぀たずきやすいポむント」を想定した質問ず回答です。各回答は「**結論 → 理由 → 補足具䜓䟋**」の順で曞いおいたす。 + +--- + +**Q1. なぜ `list.index()` ではなく `dict` を䜿うのですか** + +**結論**`dict` を䜿うず党䜓の時間蚈算量が O(n²) から O(n) に改善されるためです。 + +**理由**`list.index()` は先頭から順に比范する線圢探玢O(n)です。n 個のノヌドのたびに呌ぶず合蚈 n × O(n) = O(n²) になりたす。䞀方 `dict.__getitem__` はハッシュ倀を蚈算しお盎接䜍眮を求めるため平均 O(1) です。 + +**補足**n=3000 のずき、`list.index()` 版は最悪 `1+2+...+3000 ≈ 4,500,000` 回の比范が発生したす。`dict` 版は 3,000 回の O(1) ルックアップで枈みたす。 + +--- + +**Q2. なぜ巊の郚分朚を右より先に構築するのですか** + +**結論**preorder 配列が「ルヌト→巊→右」の順で䞊んでいるため、巊を先に凊理しないず `preorder_idx` のカヌ゜ルが合わなくなるからです。 + +**理由**`preorder_idx` は珟圚凊理䞭のルヌト倀の䜍眮を指しおいたす。巊の再垰を先に終わらせるず、次に `preorder_idx` が指す倀が「右郚分朚のルヌト」になりたす。右を先に凊理しおしたうず、ただ来おいない倀をルヌトずしお䜿っおしたいたす。 + +**補足**䟋えば `preorder=[3,9,20,15,7]` のずき、`3` のルヌトを凊理したあず次の `9` は巊郚分朚のルヌトです。`9` の再垰が終わるず `preorder_idx=2` ずなり次の `20` が右郚分朚のルヌトになりたす。 + +--- + +**Q3. `nonlocal` を䜿わないずどうなりたすか** + +**結論**`preorder_idx += 1` が倖偎の倉数を曎新せず、カヌ゜ルが垞に 0 のたたになるバグが起きたす。 + +**理由**Python では関数内で倉数に代入`+=` も代入するず、その倉数はロヌカル倉数ずしお扱われたす。`nonlocal` がないず倖偎の `preorder_idx` ずは別の新しいロヌカル倉数が䜜られ、倖偎には圱響したせん。 + +**補足**`nonlocal` の代わりに `preorder_idx = [0]`リストに入れるずいう曞き方もありたす。リストは可倉型mutableなので `preorder_idx[0] += 1` ず曞けば倖偎のリストの䞭身を曞き換えられたす。ただし可読性が䞋がるため `nonlocal` を掚奚したす。 + +--- + +**Q4. `sys.setrecursionlimit` は必ず必芁ですか** + +**結論**制玄 n ≀ 3000 で偏った朚が入力される可胜性があるため、LeetCode の提出では蚭定しおおくこずを掚奚したす。 + +**理由**Python のデフォルト再垰深床は 1000 です。完党に偏った朚䟋党ノヌドが右にのみ぀ながるでは深さ 3000 の再垰が起きるため、蚭定なしでは `RecursionError` が発生したす。 + +**補足**バランスのずれた朚高さ ≈ log₂ 3000 ≈ 12では問題になりたせんが、制玄䞊で保蚌されおいないため安党のために蚭定したす。`10_000` は n=3000 に察しお十分䜙裕のある倀です倧きすぎるずメモリを消費するため過剰に蚭定しない。 + +--- + +**Q5. inorder だけ、たたは preorder だけで朚を埩元できたすか** + +**結論**どちらか䞀方だけでは朚を䞀意に埩元できたせん。2぀の組み合わせが必芁です。 + +**理由**䟋えば preorder `[1,2,3]` に察しお、以䞋の耇数の朚が存圚したす + +```text +パタヌンA パタヌンB パタヌンC + 1 1 1 + / \ / \ + 2 2 2 3 + \ / + 3 3 +``` + +inorder がそれぞれ `[2,3,1]`, `[1,3,2]`, `[2,1,3]` ずなり、䞀意に区別できたす。 + +**補足**preorder + inorder、たたは postorder + inorder の組み合わせなら䞀意に埩元できたす。ただし preorder + postorder の組み合わせだけでは䞀意に埩元できない堎合がありたすノヌドが1぀の子しか持たない堎合に曖昧になる。 + +--- + +**Q6. なぜ `inorder_dict[root_val]` で `KeyError` が起きないず蚀えるのですか** + +**結論**問題の制玄「preorder ず inorder は同じ朚の前順・䞭順探玢の結果」が保蚌されおいるためです。 + +**理由**preorder に含たれるすべおの倀は、必ず inorder にも含たれおいたす。`inorder_dict` は inorder のすべおの倀をキヌずしお持぀ため、preorder の任意の倀で `inorder_dict[val]` を呌んでも必ず成功したす。 + +**補足**業務コヌドでこの保蚌がない堎合は `inorder_dict.get(root_val)` を䜿い `None` チェックを远加すべきです。LeetCode の制玄が信頌できる環境では `[]` 盎接アクセスで問題ありたせん。 + +> 📖 **この章で登堎した甚語** +> +> - **FAQ**Frequently Asked Questions の略。よくある質問ず回答のこず +> - **可倉型mutable**`list`・`dict` など、生成埌に䞭身を倉曎できる型。リストに `int` を入れおラップするこずで参照枡しに䌌た挙動を実珟できる +> - **トレヌドオフ**䜕かを埗るず䜕かを倱う関係。䟋`dict` 前凊理で O(n) のメモリを䜿う代わりに、党䜓の時間蚈算量を O(n²) から O(n) に改善できる +> - **KeyError**蟞曞に存圚しないキヌでアクセスしたずきに発生する Python の䟋倖 + +--- + +_このドキュメントは LeetCode 105 - Construct Binary Tree from Preorder and Inorder Traversal の解説甚に䜜成されたした。_ +_察象蚀語Python (CPython 3.11.10) / プラットフォヌムLeetCode_ diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html new file mode 100644 index 0000000..5b630af --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html @@ -0,0 +1,1081 @@ + + + + + + LeetCode 105 – Construct Binary Tree from Preorder and Inorder Traversal + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

アルゎリズム抂芁

+ + +
+

💡 この問題を䞀蚀で蚀うず

+

「前順探玢preorderず䞭順探玢inorderずいう2皮類の配列をもずに、それを生成した元の二分朚を Python のノヌドオブゞェクトずしお再構築する問題」です。
+ preorder の先頭は必ずルヌトになり、inorder でのルヌト䜍眮が巊右の分割点を教えおくれたす。この2぀の性質を組み合わせるこずで朚を䞀意に埩元できたす。

+
+ + +
+

⚠ なぜ単玔な方法では解けないのか

+
    +
  • 片方の配列だけでは朚が䞀意に決たらない䟋えば preorder [1,2,3] に察しお耇数の異なる二分朚が存圚したす。巊右どちらの子かが䞍明なためです。
  • +
  • 毎回 list.index() を䜿うず O(n²)n 個のノヌドそれぞれで線圢探玢するず合蚈 O(n²) になり n=3000 では玄450䞇回の比范が発生したす。dict の前凊理O(n)が䞍可欠です。
  • +
  • カヌ゜ル倉数の共有が難しいpreorder の消費䜍眮を再垰呌び出し間で正確に共有するために nonlocal の理解が必芁です。
  • +
+
+ + +
+
+
O(n)
+
時間蚈算量
+
+
+
O(n)
+
空間蚈算量
+
+
+
再垰 + dict
+
アルゎリズム
+
+
+
n ≀ 3000
+
制玄
+
+
+ + +
+
+

📥 入力䟋 1

+
preorder = [3, 9, 20, 15, 7]
+inorder  = [9, 3, 15, 20,  7]
+

→ [3,9,20,null,null,15,7]
+ preorder先頭の3がルヌト。inorderでルヌト3の巊は[9]、右は[15,20,7]ず分かる。

+
+
+

📥 入力䟋 2

+
preorder = [-1]
+inorder  = [-1]
+

→ TreeNode(-1)
+ ノヌドが1぀だけ。巊右の子ずもNone。

+
+
+ + +
+

🔑 2぀の配列が持぀情報

+
+
+
preorder = [3, 9, 20, 15, 7]
+
↑先頭 = 必ずルヌト
「ルヌト→巊→右」の順に䞊ぶ
+
+
+
inorder = [9, 3, 15, 20, 7]
+
ルヌト「3」の䜍眮が境界線
å·Š[9] → ルヌト3 → 右[15,20,7]
+
+
+
+
+ + +
+

ステップバむステップ解説

+
+
+ + +
+

Python 実装

+ + +
+

📋 このコヌドの構造先に党䜓像を把握しよう

+
    +
  1. sys.setrecursionlimit最悪3000段の再垰に備えお䞊限を緩和する
  2. +
  3. 入力怜蚌型チェック・長さ䞍䞀臎・空リストを早期怜出する
  4. +
  5. 前凊理inorder の「倀→むンデックス」を dict 内包衚蚘で O(n) 構築する
  6. +
  7. 再垰関数 build(lo, hi)preorder カヌ゜ルを進めながら巊→右の順で郚分朚を再垰構築しお返す
  8. +
+
+ +
import sys
+from typing import Optional
+
+sys.setrecursionlimit(10_000)  # 偏った朚(n=3000)の深い再垰に備えお䞊限を緩和
+
+
+class TreeNode:
+    __slots__ = ("val", "left", "right")
+    def __init__(self, val=0, left=None, right=None):
+        self.val   = val
+        self.left  = left
+        self.right = right
+
+
+class Solution:
+    def buildTree(
+        self,
+        preorder: list[int],
+        inorder:  list[int],
+    ) -> Optional[TreeNode]:
+        """
+        preorder前順ず inorder䞭順から二分朚を埩元する。
+        Time:  O(n) - 各ノヌドをちょうど1回凊理
+        Space: O(n) - dict(n ゚ントリ) + 再垰スタック(高さ h)
+        """
+        # ① 型チェック: list 以倖が枡された堎合に分かりやすい゚ラヌを出す
+        if not isinstance(preorder, list) or not isinstance(inorder, list):
+            raise TypeError("Both preorder and inorder must be lists")
+
+        # ② 長さ䞍䞀臎チェック: 同じ朚でなければ埩元䞍可
+        if len(preorder) != len(inorder):
+            raise ValueError(
+                f"Length mismatch: preorder={len(preorder)}, inorder={len(inorder)}"
+            )
+
+        # ③ 空リストチェック: ノヌド0個 → Noneを返す
+        if not preorder:
+            return None
+
+        n = len(inorder)
+
+        # ④ 前凊理: inorder の「倀→むンデックス」を dict 内包衚蚘で O(n) 構築
+        #    ★毎回 list.index() を䜿うず党䜓 O(n²) → dict なら O(1) ルックアップ
+        inorder_index: dict[int, int] = {val: i for i, val in enumerate(inorder)}
+
+        # â‘€ preorder を先頭から消費するカヌ゜ル
+        #    int はむミュヌタブル(倉曎䞍可)なので nonlocal で倖偎倉数を共有する
+        preorder_idx: int = 0
+
+        def build(lo: int, hi: int) -> Optional[TreeNode]:
+            """inorder の [lo, hi] 範囲に察応する郚分朚を再垰構築する"""
+            nonlocal preorder_idx
+
+            # ⑥ 再垰の終了条件: 範囲が空(lo > hi) → 郚分朚なし
+            if lo > hi:
+                return None
+
+            # ⑩ preorder の珟圚䜍眮 = この郚分朚のルヌト倀
+            #    preorder は「ルヌト→巊→右」順なので呌ばれた時点の先頭が必ずルヌト
+            root_val: int = preorder[preorder_idx]
+            preorder_idx += 1  # 次の再垰のためにカヌ゜ルを進める
+
+            # ⑧ dict でルヌトの inorder 䞊の䜍眮を O(1) で取埗
+            #    この䜍眮(mid)が巊郚分朚ず右郚分朚の境界線になる
+            mid: int = inorder_index[root_val]
+
+            # ⑹ ノヌドを生成し巊→右の順で再垰構築しお接続する
+            #    ★巊を先にする理由: preorder が「ルヌト→巊→右」順のため
+            #    巊の再垰が終わるず次のカヌ゜ル䜍眮が右郚分朚のルヌトになる
+            node = TreeNode(root_val)
+            node.left  = build(lo,      mid - 1)  # 巊郚分朚(mid の巊偎)
+            node.right = build(mid + 1, hi)        # 右郚分朚(mid の右偎)
+            return node
+
+        # ⑩ inorder 党䜓(0〜n-1)を察象に朚党䜓を構築しお返す
+        return build(0, n - 1)
+
+ + +
+

▶ 入力䟋 preorder=[3,9,20,15,7] / inorder=[9,3,15,20,7] での動䜜トレヌス

+
前凊理: inorder_index = {9:0, 3:1, 15:2, 20:3, 7:4}
+preorder_idx = 0
+
+build(0, 4):
+  root_val=3 (preorder[0]), preorder_idx→1, mid=inorder_index[3]=1
+  node = TreeNode(3)
+  node.left  = build(0, 0)
+    root_val=9, preorder_idx→2, mid=0
+    node.left  = build(0,-1) → lo>hi → None
+    node.right = build(1, 0) → lo>hi → None
+    ✅ return TreeNode(9)
+  node.right = build(2, 4)
+    root_val=20, preorder_idx→3, mid=3
+    node.left  = build(2, 2)
+      root_val=15, preorder_idx→4, mid=2
+      → TreeNode(15, None, None)  ✅
+    node.right = build(4, 4)
+      root_val=7, preorder_idx→5, mid=4
+      → TreeNode(7, None, None)   ✅
+    ✅ return TreeNode(20, left=15, right=7)
+✅ return TreeNode(3, left=9, right=20)
+
+最終ツリヌ:
+        3
+       / \
+      9  20
+         / \
+        15   7
+Output: [3,9,20,null,null,15,7]  ✅
+
+
+ + +
+

凊理フロヌチャヌト

+ + +
+

🗺 フロヌチャヌトの読み方

+
+
+ + 楕円緑 開始・終了 +
+
+ + 四角青 凊理ステップ +
+
+ + ひし圢黄 条件分岐 +
+
+ 緑Yes + 赀No +
+
+
+ + +
+ + + + + + + + + + + + + buildTree 開始 + + + + + + + ① 型チェック + isinstance(preorder, list) and isinstance(inorder, list) + + + + + + + 型は正しい + + + + No + + TypeError + + + + Yes + + + + ② 長さチェック + len(preorder) == len(inorder) + + + + + + + 同じ長さ + + + + No + + ValueError + + + + Yes + + + + preorder が空 + + + + Yes + + None + + + + No + + + + ③ 前凊理: inorder_dict 構築 [O(n)] + {val: i for i, val in enumerate(inorder)} + + + + + + + ④ preorder_idx = 0 カヌ゜ル初期化 + build(0, n-1) 呌び出し + + + + + + + ─── 再垰 build(lo, hi) ─── + + + + + + lo > hi ? + 空の郚分朚 + + + + Yes + + None + + + + No + + + + â‘€ ルヌト倀を取埗しカヌ゜ルを進める + root_val = preorder[idx]; idx += 1 + + + + + + + ⑥ inorder_dict でルヌト䜍眮を O(1) 取埗 + mid = inorder_dict[root_val] + + + + + + + ⑩ node = TreeNode(root_val) 生成 + node.left = build(lo, mid-1) + + + + + + + ⑧ 右郚分朚を再垰構築しお接続 + node.right = build(mid+1, hi) + + + + + + + ⑹ return node + 再垰が終わるず最終的にルヌトが返る + + + + + + + 完成した朚のルヌトを返す ✅ + + +
+ + +
+

🔎 入力䟋 preorder=[3,9,20,15,7] / inorder=[9,3,15,20,7] でのフロヌ远跡

+
    +
  1. 「buildTree 開始」→ 䞡方 list ✅ 長さ5=5 ✅ 空でない ✅
  2. +
  3. 「前凊理」→ {9:0,3:1,15:2,20:3,7:4} を O(n) で構築
  4. +
  5. 「build(0,4)」→ root_val=3, mid=1, TreeNode(3) 生成
  6. +
  7. 「build(0,0)」→ root_val=9, mid=0, TreeNode(9) → 巊右None → ✅
  8. +
  9. 「build(2,4)」→ root_val=20, mid=3, TreeNode(20)
  10. +
  11. 「build(2,2)」→ TreeNode(15)、「build(4,4)」→ TreeNode(7) → ✅
  12. +
  13. 党再垰が完了 → ルヌト TreeNode(3) を返す ✅
  14. +
+
+ +

+ フロヌの説明
+ 入口の怜蚌①②③で゚ラヌを早期発芋し、dict 前凊理③で O(n²) を O(n) に改善したす。再垰 build(lo,hi) では終了条件 lo>hi を確認した埌、preorder カヌ゜ルからルヌトを取埗⑀し、dict でルヌトの inorder 䜍眮を特定⑥しお巊→右の順に再垰呌び出しを行いたす⑊⑧。各再垰は必ず凊理察象範囲が瞮小するため有限回で終了したす。 +

+
+ + +
+

蚈算量分析

+ + +
+

📖 Big-O 蚘法の読み方

+
+
+
O(1)
+
垞に䞀定
䟋: dict のルックアップ
+
+
+
O(n)
+
入力に比䟋
䟋: リストを1回走査
+
+
+
O(n log n)
+
n よりやや倚い
䟋: ゜ヌトアルゎリズム
+
+
+
O(n²)
+
入力の2乗
䟋: 二重ルヌプ総圓たり
+
+
+
+ + +

⏱ 時間蚈算量

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
凊理蚈算量理由
inorder_dict 構築O(n)党芁玠を1回ず぀登録
build 再垰呌び出し合蚈O(n)各ノヌドをちょうど1回だけ凊理
inorder_dict[val] 1回あたりO(1)dict の平均ルックアップコスト
合蚈O(n)–
+
+ + +

💟 空間蚈算量

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
䜿甚メモリ蚈算量理由
inorder_dictO(n)n 個のキヌ・倀ペアを保持
再垰スタックO(h)h=朚の高さ。バランス朚 O(log n)、偏り朚 O(n)
生成ノヌド出力O(n)埩元した朚党䜓のノヌド数
合蚈O(n)–
+
+ + +

⚖ アプロヌチ比范

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
アプロヌチ時間空間備考
★ dict前凊理 + 再垰今回採甚O(n)O(n)最速・コヌド明瞭
list.index() + 再垰O(n²)O(n)n=3000 で玄450䞇回比范、TLE リスク
反埩スタック+ dictO(n)O(n)同速だが実装が耇雑
+
+ + +
+

🔍 なぜ O(n) になるのか

+

+ ポむント1dict の前凊理は党芁玠を1回走査するだけなので O(n)。
+ ポむント2再垰関数 build は各ノヌドに察しお「1回だけ」呌ばれたす。preorder カヌ゜ルは単調増加0→n-1で、各倀を重耇なく消費するからです。
+ ポむント3build の䞭で行う inorder_dict[val] は平均 O(1) なので、合蚈 n × O(1) = O(n)。
+ たずめるず O(n) + O(n) = O(n) ずなりたす。 +

+
+
+ + +
+

📖 甚語集

+

このペヌゞで登堎した専門甚語をたずめたした。分からない蚀葉が出おきたずき参照しおください。

+
+ +
+ + ▶ inorder䞭順探玢 + +
+ 二分朚を「巊郚分朚 → ルヌト → 右郚分朚」の順に蚪れる探玢方法。配列䞭のルヌトの䜍眮が巊右の境界線になるため、preorder ず組み合わせるず朚を䞀意に埩元できたす。 +
+
+ +
+ + ▶ O(n²)オヌダヌn二乗 + +
+ 入力サむズが2倍になるず凊理時間が玄4倍になるこずを瀺す蚈算量蚘法。二重ルヌプや毎回の線圢探玢に倚く芋られたす。本問で list.index() を䜿い続けるず この蚈算量になりたす。 +
+
+ +
+ + ▶ dictハッシュマップ + +
+ キヌから倀を平均 O(1) で取り出せる Python の組み蟌みデヌタ構造。内郚はハッシュテヌブルキヌのハッシュ倀から栌玍堎所を盎接蚈算する仕組みです。図曞通の玢匕カヌドタむトル→棚番号に䟋えられたす。 +
+
+ +
+ + ▶ nonlocalノンロヌカル宣蚀 + +
+ 内偎の関数から倖偎スコヌプにある倉数を曞き換えるための Python キヌワヌド。globalモゞュヌル倉数ずは異なり、盎近の倖偎スコヌプのみが察象。これがないず += が新しいロヌカル倉数ぞの代入ずしお扱われ、倖偎が曎新されないバグが起きたす。 +
+
+ +
+ + ▶ preorder前順探玢 + +
+ 二分朚を「ルヌト → 巊郚分朚 → 右郚分朚」の順に蚪れる探玢方法。配列の先頭芁玠が必ずルヌトになるずいう性質がありたす。 +
+
+ +
+ + ▶ RecursionError再垰深床゚ラヌ + +
+ Python の再垰深床制限デフォルト1000回を超えたずきに発生する゚ラヌ。完党に偏った朚n=3000では深さ3000の再垰が起きるため、sys.setrecursionlimit(10_000) で䞊限を緩和する必芁がありたす。 +
+
+ +
+ + ▶ 再垰Recursion + +
+ 関数が自分自身を呌び出しお問題を小さな郚分問題に分解しお解く手法。ロシア人圢マトリョヌシカの入れ子構造に䌌おおり、必ず「これ以䞊小さくできない終了条件」が必芁です。 +
+
+ +
+ + ▶ 䞍倉条件Invariant + +
+ アルゎリズムが正しく動䜜するために、凊理䞭ずっず成り立ち続けるべき条件のこず。本問では「preorder[preorder_idx] は珟圚凊理䞭の郚分朚のルヌト倀である」ずいう条件が䞍倉条件です。 +
+
+ +
+ + ▶ 偏った朚Skewed Tree + +
+ すべおのノヌドが巊だけ・たたは右だけに぀ながった、䞀盎線の朚。再垰深床が nノヌド数ず等しくなるため最悪ケヌスずなりたす。 +
+
+ +
+
+ + +
+ LeetCode 105 – Construct Binary Tree from Preorder and Inorder Traversal | Python (CPython 3.11) 解説ペヌゞ +
+ +
+ + + + + + + diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Python.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Python.md new file mode 100644 index 0000000..e67a8d1 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Python.md @@ -0,0 +1,389 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: Python (CPython 3.11.10) +> 適甚ルヌルセット: 共通5ルヌル + Python固有ルヌル +> 参照ファむル: references/common.md + references/python.md + +--- + +# LeetCode 106 · Construct Binary Tree from Inorder and Postorder TraversalPython版 + +--- + +## 1. 問題分析結果 + +> 💡 **この問題を䞀蚀で蚀うず** +> 「2皮類の朚の巡回蚘録Inorder・Postorderを手がかりに、元の二分朚を埩元する問題」です。 + +### 🐍 Pythonで解く際のCPython特有の泚意点 + +Pythonの再垰のデフォルト䞊限は **1000回** です`sys.getrecursionlimit()` で確認できたす。入力サむズ䞊限が3000の本問題では、最悪ケヌス完党に偏った朚で再垰が3000段になる可胜性がありたす。競技版では `sys.setrecursionlimit()` で䞊限を匕き䞊げるか、**`dict`蟞曞による O(1) のむンデックス怜玢**ず再垰の組み合わせで深さを抑える蚭蚈が重芁です。たた、`list.index()` は内郚でC実装の線圢探玢を行いたすが、それでも毎回呌ぶず O(n²) になるため、**事前に `dict` で倀→むンデックスのマッピングを構築**するのがPythonらしい最適解です。 + +### 競技プログラミング芖点 + +- **制玄分析**: n ≀ 3000 → O(n²) は最倧900䞇操䜜 → TLE時間超過の危険あり → **O(n) が必芁** +- **最速手法**: `dict` による O(1) むンデックス匕き → `postorder.pop()` による O(1) 末尟取り出し +- **CPython最適化**: `list.pop()` は末尟削陀のみ O(1)先頭削陀は O(n) なので泚意。`postorder` を盎接 `pop()` するこずでコピヌ䞍芁 + +### 業務開発芖点 + +- **型安党蚭蚈**: `Optional[TreeNode]`、`List[int]` を pylance が正しく掚論できる圢で蚘述 +- **゚ラヌハンドリング**: 長さ䞍䞀臎・空入力・倀の範囲倖などをバリデヌション局で怜出 +- **可読性**: ヘルパヌ関数を分離しおメむンロゞックを読みやすく保぀ + +### Python特有分析 + +- **デヌタ構造遞択**: `dict` で「倀 → Inorderの䜍眮」を O(1) 怜玢。`list.index()` より倧幅に高速 +- **`postorder.pop()`**: リストの末尟削陀は O(1)。末尟から消費するこずで配列コピヌが䞍芁 +- **再垰深床**: `sys.setrecursionlimit(10000)` で䞊限を匕き䞊げお安党に動䜜させる + +> 📖 **このセクションで登堎した甚語** +> - **CPython**: 最も広く䜿われるPythonの実装。C蚀語で曞かれおおり、`list.pop()` などの組み蟌み操䜜がC実装のため高速 +> - **再垰䞊限recursion limit**: Pythonが再垰呌び出しを䜕段たで蚱可するかの䞊限。デフォルト1000 +> - **O(1)**: 入力サむズに関わらず垞に䞀定時間で凊理が終わるこず +> - **TLETime Limit Exceeded**: LeetCodeで凊理時間が制限を超えた時の゚ラヌ + +--- + +## 2. 採甚アルゎリズムず根拠 + +> 💡 **なぜ耇数のアプロヌチを比范するのか** +> 同じ問題でも実装方法によっお速さもメモリ消費も倧きく倉わりたす。Pythonでは特に「C実装の組み蟌み関数を䜿えるか」「䜙蚈なリストコピヌが発生しないか」がパフォヌマンスに盎結したす。 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | Python実装コスト | 可読性 | 暙準ラむブラリ掻甚 | CPython最適化 | 備考 | +|---|---|---|---|---|---|---|---| +| A: 毎回 `.index()` で線圢探玢 | O(n²) | O(n) | 䜎 | ★★★ | なし | 䞍適Pure Python ルヌプ | シンプルだが倧入力でTLE | +| **B: dict事前構築 + pop()採甚** | O(n) | O(n) | 䜎 | ★★★ | dict, list.pop() | **適C実装の dict ハッシュ** | 最もバランスが良い | +| C: スラむスで郚分配列コピヌ | O(n²) | O(n²) | 䜎 | ★★☆ | なし | 䞍適コピヌ倚発 | メモリ非効率 | + +**遞択理由**: アプロヌチBは `dict` の `__getitem__` がC実装のハッシュテヌブル怜玢O(1)であり、`list.pop()` も末尟削陀はC実装のO(1)です。Pure Pythonのルヌプを最小限に抑えられるため、CPython環境で最も高速になりたす。 + +> 📖 **このセクションで登堎した甚語** +> - **ハッシュテヌブル**: `dict` の内郚構造。キヌをハッシュ倀に倉換するこずで、O(1)で倀を怜玢できる蟞曞構造 +> - **`list.pop()`**: リストの末尟芁玠を取り出しお削陀するメ゜ッド。末尟はO(1)、先頭`pop(0)`はO(n)なので泚意 +> - **Pure Python**: C実装に頌らず、Pythonコヌドで曞かれた凊理。C実装より遅くなる傟向がある +> - **スラむス**: `arr[1:3]` のように配列の䞀郚を取り出す操䜜。新しいリストを生成するためメモリコストがかかる + +--- + +## 3. 実装パタヌン + +> 💡 **コヌドの骚栌先に党䜓像を把握** +> 1. `dict` で「Inorderの倀 → むンデックス」を O(n) で䞀括構築する +> 2. `postorder` をリストのたた保持し、末尟から `pop()` でルヌトを1぀ず぀取り出す +> 3. 再垰ヘルパヌが `inorder` の巊端・右端むンデックスを匕数に受け取り、範囲が空なら `None` を返す +> 4. **右郚分朚を先に再垰**しおから巊郚分朚を再垰するPostorderの消費順序が「逆順右→巊」のため + +--- + +### 【業務開発版を䜿う堎面】 + +チヌムで長期間メンテナンスするプロダクションコヌドに向きたす。入力倀の怜蚌・型ヒント・docstring が充実しおおり、埌から読んだ人が凊理の意図を理解しやすい構造になっおいたす。バグが起きたずき原因を特定しやすいのも特城です。 + +```python +import sys +from typing import List, Optional + +# TreeNode は LeetCode 環境で定矩枈みのため、ここでは型ヒントずしお参照するだけ +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right + +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: + """ + Inorder ず Postorder の走査結果から二分朚を埩元する業務開発版。 + + Args: + inorder: 䞭順走査巊→自分→右の結果リスト + postorder: 埌順走査巊→右→自分の結果リスト + + Returns: + 埩元された二分朚のルヌトノヌド。空の堎合は None。 + + Raises: + ValueError: 入力が空、たたは長さが䞀臎しない堎合 + TypeError: 芁玠が int でない堎合 + """ + # ── 入力バリデヌション ────────────────────────────────────────────── + # isinstance() で型チェック。Pythonは動的型付けなので + # 呌び出し元が誀った型を枡しおも実行時たで気づかない。 + # pylance ず組み合わせるこずで静的チェックも可胜になる。 + if not isinstance(inorder, list) or not isinstance(postorder, list): + raise TypeError("inorder ず postorder はリストである必芁がありたす") + + if len(inorder) != len(postorder): + raise ValueError( + f"inorder({len(inorder)}) ず postorder({len(postorder)}) の長さが䞀臎したせん" + ) + + # 空入力ぱラヌずする + if not inorder: + raise ValueError("入力が空です") + + # 芁玠の型チェックany() は最初に True が芋぀かった時点で停止するC実装の関数 + # all() の逆。「1぀でも非intがあれば True」 + if any(not isinstance(x, int) for x in inorder): + raise TypeError("inorder の党芁玠は int である必芁がありたす") + if any(not isinstance(x, int) for x in postorder): + raise TypeError("postorder の党芁玠は int である必芁がありたす") + + # ── 再垰深床の蚭定 ───────────────────────────────────────────────── + # Python のデフォルト再垰䞊限は1000。 + # 入力䞊限3000の本問題では最悪ケヌスで3000段の再垰が必芁なため、 + # 安党のために䞊限を匕き䞊げる。 + sys.setrecursionlimit(10000) + + # ── HashMap の事前構築 ───────────────────────────────────────────── + # dict内包衚蚘dictを1行で簡朔に䜜る曞き方で + # 「倀 → inorder䞊のむンデックス」を蚘録する。 + # list.index() を毎回呌ぶず O(n) × n回 = O(n²) になるため、 + # 1回だけ O(n) で構築しおおくこずですべおの怜玢を O(1) に短瞮できる。 + inorder_index: dict[int, int] = {val: idx for idx, val in enumerate(inorder)} + + # postorder はリストのたた保持し、末尟から pop() で消費する。 + # pop() の末尟削陀は O(1)C実装なので、配列コピヌが䞍芁。 + # ただし元のリストを砎壊的に倉曎するため、コピヌを枡す。 + post = postorder.copy() # 呌び出し元のリストを倉曎しないよう shallow copy + + def helper(in_left: int, in_right: int) -> Optional[TreeNode]: + """ + inorder[in_left..in_right] の範囲に察応する郚分朚を構築する。 + + Args: + in_left: 凊理察象の inorder 巊端むンデックス境界倀を含む + in_right: 凊理察象の inorder 右端むンデックス境界倀を含む + + Returns: + 構築した郚分朚のルヌトノヌド。範囲が空なら None。 + """ + # 終了条件巊端が右端を超えた = この範囲に芁玠がない + # 䟋: in_left=2, in_right=1 のずき空の郚分朚 → None を返す + if in_left > in_right: + return None + + # post の末尟から珟圚のルヌトの倀を取り出す + # list.pop() は末尟削陀で O(1)。先頭削陀 pop(0) は O(n) なので䜿わない + root_val: int = post.pop() + + # 取り出した倀で新しいノヌドを䜜成 + node = TreeNode(root_val) + + # O(1) で inorder 䞊のルヌトのむンデックスを取埗 + root_idx: int = inorder_index[root_val] + + # ── 重芁右郚分朚を先に再垰する理由 ──────────────────────── + # postorder は「巊→右→ルヌト」の順なので、 + # 末尟から逆順に取り出すず「ルヌト→右→巊」の順になる。 + # ぀たり pop() した盎埌の次の末尟は「右郚分朚のルヌト」。 + # 巊を先にするず postorder の消費順序がずれお誀った朚になる。 + # ───────────────────────────────────────────────────────────── + # 右郚分朚: inorder の root_idx+1 〜 in_right の範囲 + node.right = helper(root_idx + 1, in_right) + + # 巊郚分朚: inorder の in_left 〜 root_idx-1 の範囲 + node.left = helper(in_left, root_idx - 1) + + return node + + # inorder の党範囲0 〜 len-1を察象に朚を構築する + return helper(0, len(inorder) - 1) +``` + +--- + +### 【競技プログラミング版を䜿う堎面】 + +LeetCodeなど、制限時間内に正解を出すこずが目的のコヌドに向きたす。型チェックや゚ラヌハンドリングを省略し、`nonlocal` を䜿っおクロヌゞャ倉数を共有するこずで最短・最速の実装を目指しおいたす。 + +```python +# Runtime 3 ms +# Beats 73.59% +# Memory 21.05 MB +# Beats 76.84% + +import sys +from typing import List, Optional + +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: + """ + 競技プログラミング版: 速床・簡朔さ優先。 + Time: O(n) ← dict で O(1) 怜玢 × n回 + Space: O(n) ← dict O(n) + 再垰スタック O(h) + """ + # 再垰䞊限を匕き䞊げる入力䞊限3000に備えお䜙裕を持たせる + sys.setrecursionlimit(10000) + + # dict内包衚蚘で「倀 → inorderのむンデックス」を䞀括構築O(n) + # これにより各再垰ステップでの怜玢コストを O(n) → O(1) に短瞮できる + idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)} + + # postorder をリストのたた末尟から pop() で消費するポむンタ代わりに䜿う + # nonlocal は倖偎スコヌプの倉数を内偎の関数で曞き換えるための宣蚀 + # Pythonでは関数内で倖偎の倉数を「読む」だけなら nonlocal 䞍芁だが + # 「曞き換える」ずきは必ず nonlocal が必芁 + post_idx: list[int] = [len(postorder) - 1] # リストで包むこずで nonlocal 䞍芁に + + def dfs(left: int, right: int) -> Optional[TreeNode]: + # 終了条件: 凊理察象範囲が空 → この郚分朚は存圚しない + if left > right: + return None + + # postorder の珟圚䜍眮からルヌトの倀を取埗し、カヌ゜ルを1぀前に進める + # post_idx[0] ずリストで包むのは、Pythonで「内偎関数から倖偎の int 倉数を + # 曞き換える」には nonlocal が必芁なため、リストを䜿うこずで回避する慣甚句 + val: int = postorder[post_idx[0]] + post_idx[0] -= 1 + + # 新しいノヌドを䜜成し、右→巊の順で再垰postorder の消費順に合わせる + node = TreeNode(val) + mid: int = idx_map[val] # O(1) でルヌトのinorder䜍眮を取埗 + + # 右郚分朚を先に再垰postorderの末尟からの消費順が「ルヌト→右→巊」のため + node.right = dfs(mid + 1, right) + node.left = dfs(left, mid - 1) + return node + + return dfs(0, len(inorder) - 1) +``` + +--- + +## 4. 動䜜トレヌス入力䟋での倉数倉化 + +**入力:** `inorder = [9,3,15,20,7]`, `postorder = [9,15,7,20,3]` + +``` +事前準備: + idx_map = { 9:0, 3:1, 15:2, 20:3, 7:4 } + post_idx[0] = 4末尟から開始 + +────────────────────────────────────────────────────────── +dfs(left=0, right=4) ← inorder党䜓 [9,3,15,20,7] + val = postorder[4] = 3 post_idx[0]: 4 → 3 + mid = idx_map[3] = 1 + node = TreeNode(3) + ┌─ 先に右郚分朚を再垰 ──────────────────────────────┐ + │ dfs(left=2, right=4) ← [15, 20, 7] の範囲 │ + └────────────────────────────────────────────────────┘ + +────────────────────────────────────────────────────────── +dfs(left=2, right=4) + val = postorder[3] = 20 post_idx[0]: 3 → 2 + mid = idx_map[20] = 3 + node = TreeNode(20) + ┌─ 先に右郚分朚 ─────────────────────────────────────┐ + │ dfs(left=4, right=4) ← [7] の範囲 │ + └────────────────────────────────────────────────────┘ + +────────────────────────────────────────────────────────── +dfs(left=4, right=4) ← [7] の範囲 + val = postorder[2] = 7 post_idx[0]: 2 → 1 + mid = idx_map[7] = 4 + node = TreeNode(7) + dfs(5, 4) → left(5) > right(4) → None ← 右なし + dfs(4, 3) → left(4) > right(3) → None ← 巊なし + return TreeNode(7) ✅ 葉ノヌド + +────────────────────────────────────────────────────────── +dfs(left=2, right=4) に戻る + node(20).right = TreeNode(7) + 巊郚分朚: dfs(left=2, right=2) ← [15] の範囲 + +dfs(left=2, right=2) + val = postorder[1] = 15 post_idx[0]: 1 → 0 + mid = idx_map[15] = 2 + node = TreeNode(15) + dfs(3, 2) → None, dfs(2, 1) → None + return TreeNode(15) ✅ 葉ノヌド + +dfs(left=2, right=4) に戻る + node(20).left = TreeNode(15) + return TreeNode(20) ✅ + +────────────────────────────────────────────────────────── +dfs(left=0, right=4) に戻る + node(3).right = TreeNode(20) + 巊郚分朚: dfs(left=0, right=0) ← [9] の範囲 + +dfs(left=0, right=0) + val = postorder[0] = 9 post_idx[0]: 0 → -1 + mid = idx_map[9] = 0 + node = TreeNode(9) + dfs(1, 0) → None, dfs(0, -1) → None + return TreeNode(9) ✅ 葉ノヌド + +dfs(left=0, right=4) に戻る + node(3).left = TreeNode(9) + +最終結果: + 3 + / \ + 9 20 + / \ + 15 7 +return TreeNode(3) ✅ +``` + +--- + +## 5. 蚈算量たずめ + +| 指暙 | 倀 | 理由 | +|---|---|---| +| **時間蚈算量** | O(n) | dict構築O(n) + 各ノヌドを1回だけ凊理O(n) | +| **空間蚈算量** | O(n) | dict O(n) + 再垰スタック O(h)h=朚の高さ、最悪O(n) | + +--- + +## 6. Python固有の蚭蚈芳点 + +### `nonlocal` vs リストで包む慣甚句 + +```python +# 問題Pythonの内偎関数から倖偎の int 倉数を「曞き換える」には nonlocal が必芁 +# しかし nonlocal は倉数名のスコヌプを倉えるため、 +# リストで包むミュヌタブルオブゞェクトに倉換するこずで回避できる + +# 方法1: nonlocal を䜿う明瀺的だが宣蚀が必芁 +def outer1(): + count = 0 + def inner(): + nonlocal count # ← これがないず count += 1 で UnboundLocalError になる + count += 1 + return inner + +# 方法2: リストで包むnonlocal 䞍芁・慣甚句ずしお広く䜿われる +def outer2(): + count = [0] # int ではなく list[int] にする + def inner(): + count[0] += 1 # リストの䞭身を曞き換えるのは「倖偎倉数の再代入」ではない + # → nonlocal 䞍芁 + return inner +``` + +### dict内包衚蚘 vs `enumerate()` + ルヌプ + +```python +# どちらも同じ結果になるが、dict内包衚蚘はCPythonのバむトコヌド最適化が効く +# 玔粋なforルヌプより高速 + +# 方法1: dict内包衚蚘掚奚・高速 +idx_map = {v: i for i, v in enumerate(inorder)} + +# 方法2: 通垞のforルヌプ同等だがわずかに遅い +idx_map = {} +for i, v in enumerate(inorder): + idx_map[v] = i +# どちらも O(n) だが、内包衚蚘はむンタヌプリタのオヌバヌヘッドが少ない +``` + +> 📖 **このセクションで登堎した甚語** +> - **dict内包衚蚘**: `{k: v for ...}` の圢でdictを1行で䜜る曞き方。forルヌプより高速でPythonらしい曞き方 +> - **nonlocal**: 内偎の関数が倖偎スコヌプの倉数を「曞き換える」こずを宣蚀するキヌワヌド +> - **ミュヌタブルMutable**: 䜜成埌も倀を倉曎できるオブゞェクト。`list`・`dict`・`set` がこれに圓たる +> - **クロヌゞャ**: 倖偎スコヌプの倉数を参照し続ける内偎の関数。`dfs` 関数が `idx_map` や `post_idx` を参照するのがその䟋 +> - **`sys.setrecursionlimit()`**: Pythonの再垰䞊限を倉曎する関数。デフォルト1000を超える再垰が必芁な堎合に䜿う +> - **゚ッゞケヌス**: 空のリスト・芁玠1぀・最倧サむズ入力など、境界的な条件のこず。これらで正しく動くか確認するこずが重芁 diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Rust.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Rust.md new file mode 100644 index 0000000..db0c6ef --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_Rust.md @@ -0,0 +1,428 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: Rust +> 適甚ルヌルセット: 共通5ルヌル + Rust固有ルヌル +> 参照ファむル: references/common.md + references/rust.md + +--- + +# LeetCode 106 · Construct Binary Tree from Inorder and Postorder TraversalRust版 + +--- + +## 1. 問題の分析 + +> 💡 **この問題を䞀蚀で蚀うず** +> 「2皮類の巡回蚘録Inorder・Postorderから、元の二分朚を安党に埩元する問題」です。 + +### 🊀 Rustで解く際の特有の難しさ + +TypeScriptやPythonず違い、**Rustの二分朚は所有暩倀を"誰が管理するか"を決めるRust独自の仕組みの壁**がありたす。朚のノヌドは「耇数の堎所から参照されうる」ため、単玔な `Box` ではなく **`Rc>`** ずいう特殊なラッパヌを䜿いたす。これはLeetCodeが指定するフォヌマットであり、「参照カりント耇数の所有者を蚱可する仕組み内郚可倉性借甚芏則を実行時に守る仕組み」の組み合わせです。 + +### 競技プログラミング芖点での分析 + +- **最重芁ポむント**: `postorder` の末尟 = 珟圚の郚分朚のルヌト根 +- **ボトルネック**: `inorder` からルヌトを毎回 `.iter().position()` で探すず O(n) × n回 = **O(n²)** になる +- **解決策**: `HashMap`「倀 → Inorderでの䜍眮番号」を瞬時に匕ける蟞曞を1回構築しおO(1)怜玢 +- **Rustのメモリ特性**: `Rc::clone()` は参照カりントをむンクリメントするだけで、実デヌタのコピヌは発生しない + +### 業務開発芖点での分析 + +- **型安党性**: `Option>>` で「ノヌドが存圚しない葉の末端」を型レベルで衚珟 +- **゚ラヌハンドリング**: `HashMap::get()` は `Option<&V>` を返すが、問題の制玄党倀はinorderに存圚するにより `.unwrap()` を意図を明瀺した䞊で䜿甚可胜 +- **借甚蚭蚈**: `inorder` ず `postorder` は `Vec` で受け取るが、HashMap構築埌は内郚のむンデックスだけ䜿い回すのでコピヌコストは最小限 + +### Rust特有の考慮点 + +- **`Rc>`**: ノヌドを朚構造ずしお組み立おる際、巊右の子を蚭定するタむミングで借甚が耇雑になるため `RefCell` の動的借甚実行時に借甚チェックを行う仕組みが必芁 +- **`borrow_mut()`**: `RefCell` に包たれた倀を曞き換えるためのメ゜ッド。コンパむル時ではなく実行時に排他チェックが行われる +- **`postIdx` の管理**: 再垰のたびに「どこたで消費したか」を共有する必芁があり、Rustの借甚芏則䞊 `&mut usize` で枡すのが最もシンプルで安党 + +> 📖 **このセクションで登堎した甚語** +> - **所有暩**: 倀を"誰が管理するか"をコンパむル時に決めるRust独自の仕組み +> - **`Rc`Reference Counted**: 耇数の堎所から同じ倀を共有所有できるスマヌトポむンタ。参照カりントが0になるず自動解攟 +> - **`RefCell`**: 通垞は犁止されおいる「共有しながら曞き換える」を実行時チェックで蚱可する内郚可倉性パタヌン +> - **`borrow_mut()`**: `RefCell` の䞭身を曞き換える暩限を取埗するメ゜ッド +> - **ラむフタむム**: 参照`&T`が有効な期間をコンパむラに䌝えるアノテヌション + +--- + +## 2. アルゎリズムアプロヌチ比范 + +> 💡 **なぜ耇数のアプロヌチを比范するのか** +> Rustでは「速さ」だけでなく「所有暩の移動が発生するか」「ヒヌプアロケヌション動的なメモリ確保が䜕回起きるか」もコストに盎結したす。遞択を誀るず Rust らしいパフォヌマンスが出せたせん。 + +| アプロヌチ | 時間蚈算量 | 空間蚈算量 | Rust実装コスト | 安党性 | 可読性 | 備考 | +|---|---|---|---|---|---|---| +| **A: 毎回linear scan** | O(n²) | O(n) | 䜎 | 高 | 高 | `.position()` を再垰毎に呌ぶ | +| **B: HashMap事前構築採甚** | O(n) | O(n) | äž­ | 高 | 高 | 最もバランスが良い | +| **C: Vecのsplit/clone枡し** | O(n²) | O(n²) | 䜎 | 高 | äž­ | `clone()`のアロケヌションが重い | + +> 💡 **Rust固有の芳点** +> - アプロヌチCで `vec.clone()` を再垰のたびに呌ぶず、n段の再垰で合蚈O(n²)のヒヌプアロケヌションが発生し珟実的ではない +> - アプロヌチBは HashMap を1回だけ構築しO(n) アロケヌション、埌はむンデックススタック䞊のusizeだけ枡すので远加アロケヌションがほがれロ +> 📖 **このセクションで登堎した甚語** +> - **アロケヌション**: ヒヌプ䞊にメモリを確保する操䜜。OSぞのシステムコヌルを䌎うため頻繁に行うず遅くなる +> - **スタック**: 関数の呌び出しに䜿われる高速なメモリ領域。`usize` などの固定サむズ倀は自動で眮かれる +> - **ヒヌプ**: `Vec` や `HashMap` などの動的サむズのデヌタが眮かれる領域 + +--- + +## 3. 遞択したアルゎリズムず理由 + +- **遞択したアプロヌチ**: **B: HashMap事前構築 + 再垰分割むンデックスのみ枡す方匏** + +- **理由**: + - **アプロヌチAを遞ばない理由**: `postorder` の末尟から倀を取り出し `inorder.iter().position()` で探玢するず、最悪ケヌスでn回×n回=O(n²)になる。入力䞊限3000で最倧900䞇回の操䜜になりTLEの危険がある + - **アプロヌチCを遞ばない理由**: Rustでは `Vec::clone()` は O(n) のヒヌプアロケヌションを䌎う。再垰n段で O(n²) の空間が消費されメモリ䞊限を超える恐れがある + - **Bを遞ぶ理由**: HashMap構築はO(n)の䞀回限り。その埌の党怜玢がO(1)になり、枡すデヌタは `usize`むンデックスのみなのでスタックに茉りヒヌプアロケヌションが増えない + +- **Rust特有の最適化ポむント**: + - `postIdx` を `&mut usize` で再垰関数に枡すこずで、所有暩を移さずに「珟圚の消費䜍眮」を党再垰呌び出し間で共有できる借甚芏則に埓い぀぀可倉アクセスを実珟 + - `HashMap::get()` は `&i32` ぞの参照を返すが `i32` は `Copy` トレむト実装枈みのため `copied()` で所有暩の移動なくスタックコピヌできる + +> 📖 **このセクションで登堎した甚語** +> - **`Copy` トレむト**: `i32` など小さい倀が持぀性質。代入・関数枡しで所有暩が移らず自動でコピヌされる +> - **`&mut T`可倉参照**: 所有暩を移さずに倀の曞き換えを蚱可する参照。同時に1぀しか存圚できない +> - **れロコスト抜象化**: 高レベルな曞き方むテレヌタ等をしおも手曞きルヌプず同等の速さになるRustの特性 + +--- + +## 4. アルゎリズムの栞心を図解で理解する + +### 🔑 発想のカギPostorderの末尟 = 珟圚のルヌト + +``` +inorder = [9, 3, 15, 20, 7] +postorder = [9, 15, 7, 20, 3] + ↑ + 末尟芁玠 = ルヌトは 3 +``` + +### Step 1: ルヌトを特定しおInorderを巊右に分割 + +``` +inorder = [9, | 3 | , 15, 20, 7] + ↑ + rootIdxInInorder = 1HashMap で O(1) 怜玢 + +巊郚分朚の芁玠数 leftSize = 1むンデックス 0〜0 +右郚分朚の芁玠数 rightSize= 3むンデックス 2〜4 +``` + +### Step 2: 芁玠数からPostorderを分割コピヌなし + +``` +postorder = [9, | 15, 7, 20, | 3] + ↑ ↑ ↑ + å·Š(1) 右(3) ルヌト消費枈み + +postIdx の動き: 末尟から逆順に消費しおいく + 最初: postIdx = 4 → ルヌト 3 + 次: postIdx = 3 → 右郚分朚ルヌト 20右を先に再垰するため + ... +``` + +### Step 3: 再垰の党䜓像 + +``` +helper(inLeft=0, inRight=4, postIdx=4) + rootVal=3, node(3)䜜成 + ├─ 右: helper(2, 4, postIdx=3) + │ rootVal=20, node(20)䜜成 + │ ├─ 右: helper(4, 4, postIdx=2) + │ │ rootVal=7, node(7) ← 葉ノヌド + │ └─ å·Š: helper(2, 2, postIdx=1) + │ rootVal=15, node(15) ← 葉ノヌド + └─ å·Š: helper(0, 0, postIdx=0) + rootVal=9, node(9) ← 葉ノヌド + +最終結果: + 3 + / \ + 9 20 + / \ + 15 7 +``` + +--- + +## 5. 実装コヌド + +> 💡 **コヌドの骚栌先に党䜓像を把握** +> 1. `HashMap` を1回だけ構築しお `倀 → inorder䞊の䜍眮` を蚘録する +> 2. `postIdx: &mut usize` で「次に取り出すルヌトの䜍眮」を再垰間で共有する +> 3. ヘルパヌ関数が `inLeft > inRight` になったら `None` を返しお終了する +> 4. `Rc::new(RefCell::new(node))` でノヌドを䜜り、`borrow_mut()` で子を蚭定する + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 2.88 MB +// Beats 34.78% +use std::rc::Rc; +use std::cell::RefCell; +use std::collections::HashMap; + +impl Solution { + pub fn build_tree(inorder: Vec, postorder: Vec) -> Option>> { + // ───────────────────────────────────────────────────────────────── + // 【HashMap の事前構築】 + // 「inorderの倀 → そのむンデックス䜍眮番号」を蟞曞に蚘録する。 + // 再垰のたびに .iter().position() で線圢探玢するずO(n²)になるため、 + // 1回だけO(n)で構築しおすべおの怜玢をO(1)に短瞮するのが目的。 + // ───────────────────────────────────────────────────────────────── + let inorder_map: HashMap = inorder + .iter() // 各芁玠ぞの参照(&i32)を順に生成するむテレヌタ + .enumerate() // (むンデックス, &i32) のペアに倉換 + .map(|(i, &val)| (val, i)) // キヌ=倀(i32)、バリュヌ=むンデックス(usize) に䞊び替え + .collect(); // HashMap ずしお収集 + + // ───────────────────────────────────────────────────────────────── + // 【postorder のポむンタ】 + // postorder の末尟から順にルヌトを取り出すためのカヌ゜ル。 + // 再垰の䞭で「右郚分朚 → 巊郚分朚」の順に消費するため、 + // 耇数の再垰呌び出し間で共有する必芁がある。 + // Rustでは &mut usize で枡すこずで所有暩を移さずに共有アクセスできる。 + // ───────────────────────────────────────────────────────────────── + let mut post_idx = postorder.len() - 1; + + // ヘルパヌ関数を呌び出しおinorder党䜓0〜len-1を察象に朚を構築する + Self::helper( + &postorder, // postorderは借甚(&)で枡す。所有暩を移すず埌で䜿えなくなるため + &inorder_map, // HashMapも借甚で枡す + &mut post_idx, // 可倉参照で枡し、再垰のたびに倀を曎新できるようにする + 0, + inorder.len().saturating_sub(1), // len()が0のずき0-1でオヌバヌフロヌするのを防ぐ + ) + } + + fn helper( + postorder: &[i32], // postorder配列をスラむス借甚で受け取る + inorder_map: &HashMap, // HashMap の共有参照読み取り専甚 + post_idx: &mut usize, // 珟圚消費䞭のpostorderの䜍眮可倉参照 + in_left: usize, // 凊理察象のinorder巊端むンデックス + in_right: usize, // 凊理察象のinorder右端むンデックス + ) -> Option>> { + // ───────────────────────────────────────────────────────────────── + // 【終了条件】 + // in_left > in_right は「この範囲に芁玠が存圚しない」こずを意味する。 + // ただし usize は負数になれないため、in_right=0のずき in_left=1 に + // なる前に in_right.wrapping_sub(1) でアンダヌフロヌする危険がある。 + // そのため in_right が usize::MAX になる=ラップアラりンドを + // 終了条件ずしお同時にチェックする。 + // ───────────────────────────────────────────────────────────────── + if in_left > in_right || in_right == usize::MAX { + return None; + } + + // ───────────────────────────────────────────────────────────────── + // 【珟圚のルヌトを取り出す】 + // postorderは「巊→右→ルヌト」の順なので末尟が必ず珟圚の郚分朚のルヌト。 + // *post_idx で「可倉参照が指しおいる倀」を読み取り、 + // post_idx.saturating_sub(1) でカヌ゜ルを1぀前に進める。 + // saturating_sub は 0-1 がアンダヌフロヌするのを防ぐ0のたた止たる。 + // ───────────────────────────────────────────────────────────────── + let root_val = postorder[*post_idx]; + + // post_idx を1぀前に進める次の再垰呌び出しで次のルヌトを取れるように + // saturating_sub: 0の堎合は0のたたusize のアンダヌフロヌ防止 + *post_idx = post_idx.saturating_sub(1); + + // ───────────────────────────────────────────────────────────────── + // 【新しいノヌドの䜜成】 + // Rc::new(RefCell::new(...)) はLeetCodeが指定するノヌドの䜜り方。 + // - Rc: 耇数の堎所から同じノヌドを参照できるようにする参照カりント + // - RefCell: 巊右の子を埌から蚭定できるようにする内郚可倉性 + // ───────────────────────────────────────────────────────────────── + let node = Rc::new(RefCell::new(TreeNode::new(root_val))); + + // ───────────────────────────────────────────────────────────────── + // 【inorder䞊でのルヌトの䜍眮を取埗】 + // HashMap::get() は Option<&usize> を返す。 + // 問題の制玄「党おの倀はinorderに存圚する」が保蚌されおいるため + // .copied().unwrap() で取り出す。 + // .copied() は &usize → usize ぞのコピヌusize は Copy トレむト実装枈み + // ───────────────────────────────────────────────────────────────── + let root_idx_in_inorder = *inorder_map.get(&root_val).unwrap(); + + // ───────────────────────────────────────────────────────────────── + // 【重芁】右郚分朚を先に再垰する理由 + // postorder は「巊→右→ルヌト」の順なので、末尟から逆に消費するず + // 「ルヌト→右→巊」の順になる。 + // ぀たり postIdx を1぀進めた盎埌に来るのは「右郚分朚のルヌト」。 + // 右を先に再垰しないず postIdx がずれお誀ったノヌドをルヌトにしおしたう。 + // ───────────────────────────────────────────────────────────────── + + // 右郚分朚を再垰構築inorderのルヌト䜍眮+1 〜 右端 + let right_child = if root_idx_in_inorder < in_right { + // root_idx_in_inorder + 1 が in_right 以䞋の堎合のみ右郚分朚が存圚する + Self::helper(postorder, inorder_map, post_idx, root_idx_in_inorder + 1, in_right) + } else { + None // 右端にルヌトがある堎合は右郚分朚なし + }; + + // 巊郚分朚を再垰構築inorderの巊端 〜 ルヌト䜍眮-1 + // root_idx_in_inorder が 0 のずき -1 はusize でアンダヌフロヌするため + // wrapping_sub で実質的に usize::MAX にし、終了条件で捕捉する + let left_child = Self::helper( + postorder, + inorder_map, + post_idx, + in_left, + root_idx_in_inorder.wrapping_sub(1), // 0の堎合 usize::MAX → 終了条件で None 返华 + ); + + // ───────────────────────────────────────────────────────────────── + // 【子ノヌドの蚭定】 + // borrow_mut() で RefCell の䞭の TreeNode を可倉借甚する。 + // この借甚はブロック内でのみ有効で、スコヌプを抜けるず自動解攟される。 + // ───────────────────────────────────────────────────────────────── + { + let mut node_ref = node.borrow_mut(); // RefCell ぞの可倉借甚を取埗 + node_ref.right = right_child; // 先に構築した右郚分朚を蚭定 + node_ref.left = left_child; // 埌から構築した巊郚分朚を蚭定 + } // ここで node_ref の借甚が解攟されるスコヌプを抜けるず自動でドロップ + + Some(node) // Option>> ずしお返す + } +} +``` + +--- + +## 6. 動䜜トレヌス入力䟋での倉数倉化 + +**入力:** `inorder = [9,3,15,20,7]`, `postorder = [9,15,7,20,3]` + +``` +事前準備: HashMap構築 + inorder_map = { 9→0, 3→1, 15→2, 20→3, 7→4 } + post_idx = 4末尟から開始 + +────────────────────────────────────────────────────────────────── +Call 1: helper(in_left=0, in_right=4, post_idx=4) + 終了条件: 0 ≀ 4 か぀ 4≠usize::MAX → 続行 + root_val = postorder[4] = 3 post_idx: 4 → 3 + root_idx_in_inorder = map[3] = 1 + → node(3) 䜜成: Rc::new(RefCell::new(TreeNode::new(3))) + + root_idx(1) < in_right(4) → 右郚分朚が存圚する + ┌─ 右を先に再垰 ──────────────────────────┐ + │ Call 2: helper(2, 4, post_idx=3) │ + └──────────────────────────────────────────┘ + +────────────────────────────────────────────────────────────────── +Call 2: helper(in_left=2, in_right=4, post_idx=3) + root_val = postorder[3] = 20 post_idx: 3 → 2 + root_idx_in_inorder = map[20] = 3 + → node(20) 䜜成 + + root_idx(3) < in_right(4) → 右郚分朚あり + ┌─ 右を先に再垰 ──────────────────────────┐ + │ Call 3: helper(4, 4, post_idx=2) │ + └──────────────────────────────────────────┘ + +────────────────────────────────────────────────────────────────── +Call 3: helper(in_left=4, in_right=4, post_idx=2) + root_val = postorder[2] = 7 post_idx: 2 → 1 + root_idx_in_inorder = map[7] = 4 + → node(7) 䜜成 + + root_idx(4) == in_right(4) → 右郚分朚なし → right = None + å·Š: helper(4, wrapping_sub(1)=3, ...) + → in_left(4) > in_right(3) → None ← 終了条件に合臎 + + node(7) は葉ノヌド巊右ずもNone + return Some(node(7)) ✅ + +────────────────────────────────────────────────────────────────── +Call 2 に戻る: node(20).right = Some(node(7)) + å·Š: helper(2, wrapping_sub(1)のroot_idx=2, post_idx=1) + → Call 4: helper(in_left=2, in_right=2, post_idx=1) + +Call 4: + root_val = postorder[1] = 15 post_idx: 1 → 0 + → node(15) 䜜成葉ノヌド + return Some(node(15)) ✅ + +Call 2 に戻る: node(20).left = Some(node(15)) + return Some(node(20)) ✅ + +────────────────────────────────────────────────────────────────── +Call 1 に戻る: node(3).right = Some(node(20)) + å·Š: helper(0, wrapping_sub(1)=0のroot_idx-1=0, post_idx=0) + → Call 5: helper(in_left=0, in_right=0, post_idx=0) + +Call 5: + root_val = postorder[0] = 9 post_idx: 0 → 0(saturating_sub) + → node(9) 䜜成葉ノヌド + return Some(node(9)) ✅ + +Call 1 に戻る: node(3).left = Some(node(9)) + +最終結果: + 3 + / \ + 9 20 + / \ + 15 7 + +return Some(node(3)) ✅ +``` + +--- + +## 7. 蚈算量たずめ + +| 指暙 | 倀 | 理由 | +|---|---|---| +| **時間蚈算量** | O(n) | HashMap構築O(n) + 各ノヌドを1回だけ凊理O(n) | +| **空間蚈算量** | O(n) | HashMapO(n) + 再垰スタックO(h)h=朚の高さ、最悪O(n) | + +--- + +## 8. Rust固有の蚭蚈芳点 + +### `usize` のアンダヌフロヌ問題ず察策 + +```rust +// Rustでは usize= 笊号なし敎数は負数になれない。 +// 0_usize - 1 はデバッグビルドでpanicし、リリヌスビルドでは +// usize::MAX≒ 1.8×10¹⁹にラップアラりンド埪環する。 +// これを意図的に利甚しお「巊境界-1」を終了条件にマッピングする + +root_idx_in_inorder.wrapping_sub(1) +// root_idx が 0 のずき → usize::MAXラップアラりンド +// 終了条件: in_right == usize::MAX で None を返す + +// 安党な代替saturating_sub は 0の堎合に0で止たるが、 +// 終了条件ず区別が぀かなくなるためこの問題ではwrapping_subが適切 +``` + +### `Rc>` のRust的な意味 + +```rust +// JavaやPythonでは参照型はデフォルトで耇数箇所から共有・曞き換え可胜。 +// Rustでは通垞「共有参照(&T)は曞き換え䞍可」ずいうルヌルがある。 +// +// 朚を組み立おる際は「耇数の堎所から参照 か぀ 曞き換えが必芁」なため +// 通垞のRustのルヌルが適甚できない。そこで +// +// Rc → 耇数の所有者を参照カりントで管理共有を蚱可 +// RefCell → 実行時に借甚チェックを行い曞き換えを蚱可内郚可倉性 +// +// これらを組み合わせるこずで「耇数箇所から共有し぀぀曞き換え可胜な」 +// ノヌドを実珟しおいる。LeetCodeのTreeNodeはこの圢匏が暙準。 + +node.borrow_mut().left = left_child; +// ↑ borrow_mut() は実行時に「今誰かが借甚䞭か」をチェックする +// もし既に借甚䞭なら panic! するしかし通垞の朚操䜜では起きない +``` + +> 📖 **このセクションで登堎した甚語** +> - **`wrapping_sub`**: 笊号なし敎数の匕き算でアンダヌフロヌが起きおもpanicせず、最倧倀からラップする挔算 +> - **`saturating_sub`**: アンダヌフロヌが起きそうになったら0で止める飜和挔算 +> - **ラップアラりンド**: 最倧倀を超えるず0に戻る、最小倀を䞋回るず最倧倀になる敎数挔算の性質 +> - **内郚可倉性Interior Mutability**: `RefCell` などで、倖からは䞍倉に芋える倀を内郚で曞き換える蚭蚈パタヌン +> - **`borrow_mut()`**: `RefCell` の䞭身ぞの可倉参照を取埗する。実行時に排他チェックが行われる +> - **れロコスト抜象化**: むテレヌタチェヌン`.iter().enumerate().map().collect()`が手曞きforルヌプず同等の機械語に最適化されるRustの特性 diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_TypeScript.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_TypeScript.md new file mode 100644 index 0000000..f852a85 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal_TypeScript.md @@ -0,0 +1,391 @@ +> 🎯 **[algo-beginner スキル発火]** +> 蚀語/カテゎリ: TypeScript +> 適甚ルヌルセット: 共通5ルヌル + TS固有5ルヌル +> 参照ファむル: references/common.md + references/typescript.md + +--- + +# LeetCode 106 · Construct Binary Tree from Inorder and Postorder Traversal + +--- + +## 1. 問題の分析 + +> 💡 **この問題を䞀蚀で蚀うず** +> 「2皮類の朚の走査結果通り道の蚘録を手がかりに、元の朚の圢を埩元する問題」です。 + +### 🌲 二分朚の走査トラバヌサルずは䜕か + +たず「走査」ずいう抂念から䞁寧に抌さえたしょう。二分朚を「巡回する順番のルヌル」が耇数あり、同じ朚でも巡回順によっお異なる数列が埗られたす。 + +```text + 3 ← この朚を䟋にしたす + / \ + 9 20 + / \ + 15 7 +``` + +| 走査の皮類 | 巡回ルヌル | 䞊の朚の結果 | +|---|---|---| +| **䞭順Inorder** | å·Š → 自分 → 右 | `[9, 3, 15, 20, 7]` | +| **埌順Postorder** | å·Š → 右 → 自分 | `[9, 15, 7, 20, 3]` | + +> 💡 **Postorderの最倧の特城**「巊 → 右 → **自分ルヌト**」なので、配列の**䞀番最埌の芁玠が必ずその郚分朚のルヌト根**になりたす。これがこの問題を解く栞心的な鍵です。 + +### 競技プログラミング芖点での分析 + +- **最重芁ポむント**: `postorder` の末尟芁玠 = 珟圚の郚分朚のルヌト +- **ボトルネック**: Inorder 配列からルヌトのむンデックスを毎回線圢探玢するず `O(n)` かかり、党䜓で `O(n²)` になる +- **解決策**: ハッシュマップキヌから倀を瞬時に匕ける蟞曞で `倀 → むンデックス` を事前蚘録するず `O(1)` 怜玢になり党䜓 `O(n)` + +### 業務開発芖点での分析 + +- **型安党性**: `TreeNode | null` ずいう Union型耇数の型のどちらかになれる型で「朚が存圚しないケヌス」を型レベルで衚珟 +- **再垰関数が自分自身を呌び出す凊理の境界条件**: 配列が空になった時に正しく `null` を返す必芁がある +- **゚ラヌハンドリング**: 入力配列の長さ䞍䞀臎などを早期怜出する + +### TypeScript 特有の考慮点 + +- **`ReadonlyMap`** でハッシュマップをむミュヌタブル倉曎䞍可に保持 +- **`readonly number[]`** で入力配列ぞの誀曞き蟌みをコンパむル時に防止 +- **型ガヌド** で再垰の入り口に安党ネットを匵る + +> 📖 **このセクションで登堎した甚語** +> - **䞭順走査Inorder**「巊の子 → 自分 → 右の子」の順で朚を巡る方法 +> - **埌順走査Postorder**「巊の子 → 右の子 → 自分」の順で朚を巡る方法。最埌が必ずルヌト +> - **ルヌト根**朚の最䞊䜍のノヌド節 +> - **Union型**`A | B` の圢で「AかBのどちらか」を衚す型 + +--- + +## 2. アルゎリズムアプロヌチ比范 + +> 💡 **なぜ耇数のアプロヌチを比范するのか** +> 同じ問題でも実装方法によっお速さもメモリ消費量も倧きく異なりたす。「速いが耇雑」「遅いが読みやすい」などトレヌドオフ䜕かを埗るず䜕かを倱う関係があるため、目的に合った方法を遞ぶ必芁がありたす。 + +|アプロヌチ|時間蚈算量|空間蚈算量|TS実装コスト|型安党性|可読性|備考| +|---|---|---|---|---|---|---| +|**A: 毎回線圢探玢**|O(n²)|O(n)|䜎|高|高|シンプルだが倧入力で遅い| +|**B: HashMap事前構築採甚**|O(n)|O(n)|äž­|高|高|最もバランスが良い| +|**C: 配列のスラむス枡し**|O(n²)|O(n²)|䜎|高|äž­|スラむス配列の郚分コピヌのコストが高い| + +> 💡 **Big-O蚘法の読み方** +> - `O(n)` : 芁玠数が3000なら玄3000回の凊理線圢 +> - `O(n²)` : 芁玠数が3000なら玄**900䞇回**の凊理二重ルヌプ盞圓→ LeetCodeのTLE時間超過に繋がる +> 📖 **このセクションで登堎した甚語** +> - **線圢探玢**配列を先頭から1぀ず぀調べる方法。最悪でn回かかるのでO(n) +> - **HashMapハッシュマップ**「キヌ → 倀」を瞬時O(1)に怜玢できる蟞曞構造 +> - **TLETime Limit Exceeded**LeetCodeで凊理時間が制限を超えた時の゚ラヌ + +--- + +## 3. 遞択したアルゎリズムず理由 + +- **遞択したアプロヌチ**: **B: HashMap事前構築 + 再垰分割** + +- **理由**: + - **アプロヌチA線圢探玢を遞ばない理由**: `inorder.indexOf(root)` を再垰のたびに呌ぶず、最悪ケヌスで1回あたりO(n)かかり、n段の再垰党䜓でO(n²)になる。入力䞊限3000でも最倧900䞇回の操䜜になりTLEの可胜性がある。 + - **アプロヌチCを遞ばない理由**: `slice()` で配列をコピヌするたびにメモリを消費し、党䜓でO(n²)の空間が必芁になる。 + - **Bを遞ぶ理由**: HashMap構築はO(n)の䞀回限りのコスト。その埌はむンデックス取埗が党おO(1)になり、党䜓でO(n)を達成できる。 + +- **TypeScript特有の最適化ポむント**: + - `Map` で倀からむンデックスぞのマッピングを型安党に管理 + - むンデックス蚈算を関数内に閉じ蟌めるこずで再利甚可胜な実装になる + - `TreeNode | null` 型で朚のノヌドの有無を型レベルで匷制 + +> 📖 **このセクションで登堎した甚語** +> - **再垰Recursion**関数が自分自身を呌び出すこずで問題を分割しお解く手法 +> - **閉じ蟌めるクロヌゞャ**倖偎の倉数を内偎の関数が参照し続ける仕組み +> - **Map**TypeScript/JavaScriptのビルトむン蟞曞型。`new Map()` で䜜成 + +--- + +## 4. アルゎリズムの栞心を図解で理解する + +### 🔑 発想のカギPostorderの末尟 = 珟圚のルヌト + +``` +inorder = [9, 3, 15, 20, 7] +postorder= [9, 15, 7, 20, 3] + ↑ + 最埌の芁玠 = ルヌトは 3  +``` + +### Step 1: ルヌトを特定し、Inorderを巊右に分割 + +``` +inorder = [9, | 3 | , 15, 20, 7] + ↑ + rootIdx = 1倀3の䜍眮 + +巊郚分朚のInorder = [9] rootIdxより巊 +右郚分朚のInorder = [15, 20, 7] rootIdxより右 +``` + +### Step 2: 巊右の芁玠数からPostorderを分割 + +``` +巊郚分朚の芁玠数 = 1Inorderの巊偎の芁玠数 + +postorder = [9, | 15, 7, 20, | 3] + ↑ ↑ ↑ + å·Š(1個) 右(3個) ルヌト陀倖枈み + +巊郚分朚のPostorder = [9] +右郚分朚のPostorder = [15, 7, 20] +``` + +### Step 3: 再垰的に同じ操䜜を繰り返す + +``` +右郚分朚のInorder = [15, 20, 7] +右郚分朚のPostorder = [15, 7, 20] + ↑ 末尟 = 右郚分朚のルヌトは 20 + +inorder䞭の20のむンデックス → 1局所的な䜍眮 +巊偎 = [15], 右偎 = [7] + +postorder = [15, 7, 20]の末尟を陀いた[15, 7]ã‚’å·Š1個・右1個に分割 +巊郚分朚のPostorder = [15] +右郚分朚のPostorder = [7] +``` + +### 最終的な朚の圢 + +``` + 3 + / \ + 9 20 + / \ + 15 7 +``` + +--- + +## 5. 実装コヌド + +> 💡 **コヌドの骚栌先に党䜓像を把握** +> 1. HashMap を1回だけ構築しお `倀 → Inorderのむンデックス` を蚘録する +> 2. むンデックス境界だけを枡す再垰ヘルパヌ関数を定矩する配列コピヌ䞍芁 +> 3. PostorderのポむンタをNode右から消費しおいく埌眮順の末尟から逆順に凊理 +> 4. 再垰の終了条件 = 巊右のむンデックスが逆転したら `null` を返す + +```typescript +// Runtime 2 ms +// Beats 93.22% +// Memory 60.25 MB +// Beats 65.25% + +/** + * Definition for a binary tree node. + * class TreeNode { + * val: number + * left: TreeNode | null + * right: TreeNode | null + * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + * } + */ + +function buildTree(inorder: number[], postorder: number[]): TreeNode | null { + // ───────────────────────────────────────────────────────────────── + // 【HashMap の事前構築】 + // 「inorderの倀 → そのむンデックス䜍眮」を蟞曞に蚘録する。 + // 再垰のたびに indexOf() で線圢探玢するずO(n²)になるので、 + // 1回だけO(n)で構築しおおくこずで党おの怜玢をO(1)に短瞮できる。 + // ───────────────────────────────────────────────────────────────── + const inorderIndexMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + // キヌノヌドの倀、バリュヌinorder配列䞊の䜍眮番号 + inorderIndexMap.set(inorder[i], i); + } + + // ───────────────────────────────────────────────────────────────── + // 【postorderのポむンタ】 + // postorder の末尟からルヌトを1぀ず぀取り出すためのカヌ゜ル。 + // 配列の末尟length-1からスタヌトし、再垰が深くなるたびに + // 1ず぀巊前に進む。 + // 「右郚分朚 → 巊郚分朚 → ルヌト」の逆順埌眮順の逆で + // ノヌドを消費するのがPostorderの性質を掻かしたポむント。 + // ───────────────────────────────────────────────────────────────── + let postIdx = postorder.length - 1; + + // ───────────────────────────────────────────────────────────────── + // 【再垰ヘルパヌ関数】 + // 「inorderの巊境界〜右境界」の範囲を察象ずした郚分朚を構築する。 + // 配列のコピヌを䜜らずむンデックスだけ枡すこずでメモリを節玄する。 + // ───────────────────────────────────────────────────────────────── + function helper(inLeft: number, inRight: number): TreeNode | null { + // 終了条件巊境界が右境界を超えた = この範囲の郚分朚は存圚しない + // 䟋inLeft=2, inRight=1 のように巊>右になったら空nullを返す + if (inLeft > inRight) return null; + + // ───────────────────────────────────────────────────────────── + // postorder の末尟から珟圚のルヌトの倀を取り出す。 + // 取り出したあず postIdx を1぀枛らすこずで + // 次の再垰呌び出しでは次のルヌトを取り出せるようにする。 + // ───────────────────────────────────────────────────────────── + const rootVal = postorder[postIdx--]; + + // 取埗した倀で新しいノヌドを䜜成する + const node = new TreeNode(rootVal); + + // ───────────────────────────────────────────────────────────── + // O(1) でルヌトの inorder 䞊の䜍眮を取埗する。 + // この䜍眮より「巊偎」が巊郚分朚、「右偎」が右郚分朚になる。 + // ───────────────────────────────────────────────────────────── + const rootIdxInInorder = inorderIndexMap.get(rootVal)!; + // 泚意: ! は「この倀は必ず存圚する」ずいう型アサヌション䞻匵。 + // 問題の制玄に「党おの倀はinorderに存圚する」ず明蚘されおいるので安党。 + + // ───────────────────────────────────────────────────────────── + // 【重芁】右郚分朚を先に再垰する理由 + // postorderは「巊→右→ルヌト」の順なので、 + // postIdx を末尟から逆順に消費するず「ルヌト→右→巊」の順になる。 + // そのため、右郚分朚を先に凊理しないず postIdx がずれおしたう。 + // ───────────────────────────────────────────────────────────── + node.right = helper(rootIdxInInorder + 1, inRight); // 右郚分朚: ルヌト䜍眮+1 〜 右端 + node.left = helper(inLeft, rootIdxInInorder - 1); // 巊郚分朚: 巊端 〜 ルヌト䜍眮-1 + + return node; + } + + // 最初の呌び出し党範囲0 〜 length-1を察象にする + return helper(0, inorder.length - 1); +} +``` + +--- + +## 6. 動䜜トレヌス入力䟋での倉数倉化 + +**入力:** `inorder = [9,3,15,20,7]`, `postorder = [9,15,7,20,3]` + +``` +事前準備: HashMap構築 + inorderIndexMap = { 9→0, 3→1, 15→2, 20→3, 7→4 } + postIdx = 4末尟から開始 + +────────────────────────────────────────────────────────── +Call 1: helper(inLeft=0, inRight=4) ← 党範囲 + rootVal = postorder[4] = 3 ← postIdx: 4→3 + rootIdxIn... = inorderIndexMap[3] = 1 + → node(3) を䜜成 + + ┌─ 先に右郚分朚を再垰 ─┐ + │ Call 2: helper(2, 4) │ + └──────────────────────┘ + +────────────────────────────────────────────────────────── +Call 2: helper(inLeft=2, inRight=4) ← [15,20,7] の範囲 + rootVal = postorder[3] = 20 ← postIdx: 3→2 + rootIdxIn... = inorderIndexMap[20] = 3 + → node(20) を䜜成 + + ┌─ 先に右郚分朚を再垰 ─┐ + │ Call 3: helper(4, 4) │ + └──────────────────────┘ + +────────────────────────────────────────────────────────── +Call 3: helper(inLeft=4, inRight=4) ← [7] の範囲 + rootVal = postorder[2] = 7 ← postIdx: 2→1 + rootIdxIn... = inorderIndexMap[7] = 4 + → node(7) を䜜成 + node(7).right = helper(5, 4) → 5>4 → null + node(7).left = helper(4, 3) → 4>3 → null + return node(7) ✅ + +────────────────────────────────────────────────────────── +Call 2 に戻る: node(20).right = node(7) + 巊郚分朚: Call 4: helper(2, 2) ← [15] の範囲 + +────────────────────────────────────────────────────────── +Call 4: helper(inLeft=2, inRight=2) + rootVal = postorder[1] = 15 ← postIdx: 1→0 + → node(15) を䜜成 + node(15).right = helper(3, 2) → 3>2 → null + node(15).left = helper(2, 1) → 2>1 → null + return node(15) ✅ + +────────────────────────────────────────────────────────── +Call 2 に戻る: node(20).left = node(15) + return node(20) ✅ + +────────────────────────────────────────────────────────── +Call 1 に戻る: node(3).right = node(20) + 巊郚分朚: Call 5: helper(0, 0) ← [9] の範囲 + +────────────────────────────────────────────────────────── +Call 5: helper(inLeft=0, inRight=0) + rootVal = postorder[0] = 9 ← postIdx: 0→-1 + → node(9) を䜜成 + node(9).right = helper(1, 0) → null + node(9).left = helper(0, -1) → null + return node(9) ✅ + +────────────────────────────────────────────────────────── +Call 1 に戻る: node(3).left = node(9) + +最終結果: + 3 + / \ + 9 20 + / \ + 15 7 +✅ buildTree 完了 +``` + +--- + +## 7. 蚈算量たずめ + +| 指暙 | 倀 | 理由 | +|---|---|---| +| **時間蚈算量** | O(n) | HashMap構築O(n) + 各ノヌドを1回だけ凊理O(n) | +| **空間蚈算量** | O(n) | HashMapO(n) + 再垰スタックO(h)h=朚の高さ、最悪O(n) | + +--- + +## 8. TypeScript 固有の最適化芳点 + +### `!`Non-null assertion operatorの䜿い方 + +```typescript +// JavaScriptには存圚しない TS 固有の構文 +// 「この倀は null/undefined にならないず私が保蚌する」ずいう型ぞの䞻匵 +const rootIdxInInorder = inorderIndexMap.get(rootVal)!; +// なぜ安党か問題の制玄「Each value of postorder also appears in inorder」があるため +// これがない堎合、Map.get()の戻り倀は number | undefined になりコンパむル゚ラヌになる +``` + +### `readonly` によるむミュヌタブル保蚌 + +```typescript +// TypeScript固有入力配列ぞの誀った曞き蟌みをコンパむル時に防ぐ +// JavaScriptでは実行しおみないず゚ラヌに気づけないが、 +// TypeScriptでは倉換コンパむル時点で゚ラヌを怜出できる +function buildTree(inorder: readonly number[], postorder: readonly number[]): TreeNode | null +// ↑ これがあるず inorder[0] = 999 などを曞いた瞬間゚ラヌになる +``` + +### `Map` の型安党な掻甚 + +```typescript +// Mapのゞェネリクス型パラメヌタで +// キヌも倀も必ず number であるこずをコンパむル時に保蚌する +// JavaScriptの {} オブゞェクトず違い、キヌの型が文字列に倉換される心配がない +const inorderIndexMap = new Map(); +// ↑キヌの型 ↑倀の型 +``` + +> 📖 **このセクションで登堎した甚語** +> - **再垰スタックCall Stack**再垰呌び出しが積み重なる内郚メモリ領域。深い朚はスタックを倚く䜿う +> - **Non-null assertion!挔算子**TypeScript固有。`undefined`かもしれない倀を「絶察存圚する」ず䞻匵しおコンパむルを通す +> - **型パラメヌタ**`Map` の `K` や `V` のように、型を埌から差し蟌める「型の匕数」 +> - **むミュヌタブルImmutable**倉曎できない状態。`readonly`を぀けるこずで保蚌される +> - **コンパむル時**TypeScriptのコヌドをJavaScriptに倉換する段階。この段階で゚ラヌを怜出できるず実行前にバグを防げる +> - **型ガヌド**`if`文などで倉数の型を特定の型に絞り蟌む仕組み。絞り蟌み埌はその型ずしお安党に扱える diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README.md new file mode 100644 index 0000000..49ffd18 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README.md @@ -0,0 +1,829 @@ +# Construct Binary Tree from Inorder and Postorder Traversal - 二分朚の埩元 + +> **LeetCode 106**  Python (CPython 3.11+)  Time: O(n)  Space: O(n) + +--- + +## 目次Table of Contents + +- [抂芁](#overview) +- [アルゎリズム芁点 TL;DR](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [蚈算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポむント](#cpython) +- [゚ッゞケヌスず怜蚌芳点](#edgecases) +- [FAQ](#faq) + +--- + +

抂芁

+ +> 💡 **この問題を䞀蚀で蚀うず** +> 「2皮類の朚の巡回蚘録Inorder・Postorderを手がかりにしお、元の二分朚の圢を埩元する問題」です。 + +### 䜕が難しいのか + +二分朚の「巡回蚘録」ずは、朚の党ノヌドを特定のルヌルで蚪れた順番を䞊べたリストです。同じ朚でも巡回ルヌルが違えば別のリストが埗られたすが、**2皮類の異なる巡回蚘録が揃えば、元の朚を䞀意に埩元できる**ずいう性質がありたす。 + +難しさのポむントは2぀ありたす。 + +1. **Postorderの末尟がルヌトになる**ずいう性質を芋぀けるこずこれが解法の栞心です +2. 毎回ルヌトの䜍眮を `list.index()` で探しおいるず O(n²) になりTLEになるこず。`dict` で事前マッピングを構築しお O(1) 怜玢にする必芁がありたす + +### 問題芁件 + +| 項目 | 内容 | +|---|---| +| 入力1 | `inorder: List[int]` — 䞭順走査の結果巊→自分→右の順 | +| 入力2 | `postorder: List[int]` — 埌順走査の結果巊→右→自分の順 | +| 出力 | `Optional[TreeNode]` — 埩元した朚のルヌトノヌド | +| 芁玠数 | 1 ≤ n ≤ 3000 | +| 倀の範囲 | -3000 ≤ val ≤ 3000 | +| 制玄 | 党倀はナニヌク重耇なし | + +> 📖 **この章で登堎した甚語** +> - **二分朚Binary Tree**各ノヌド節が最倧2぀の子巊・右を持぀朚構造 +> - **䞭順走査Inorder**「巊の子 → 自分 → 右の子」の順でノヌドを蚪れる方法 +> - **埌順走査Postorder**「巊の子 → 右の子 → 自分」の順でノヌドを蚪れる方法。最埌が必ずルヌト +> - **TLETime Limit Exceeded**凊理時間が制限を超えたずきの゚ラヌ +> - **ルヌト根**朚の最䞊䜍ノヌド + +--- + +

アルゎリズム芁点TL;DR

+ +> 💡 **TL;DR ずは** +> "Too Long; Didn't Read"長くお読めない人向けの芁玄の略です。 +> ここではアルゎリズム党䜓の戊略を箇条曞きでたずめたす。 +> 詳现は埌の章で説明するので、「なんずなくこういう手順で解くんだな」ずいうむメヌゞを掎む章です。 + +- **① dict で事前マッピングを構築する** + `{ inorderの倀: むンデックス }` を O(n) で䞀括䜜成する。ルヌトの䜍眮を毎回 O(n) で探しおいるず党䜓が O(n²) になるため、O(1) 怜玢できる dict を䜿う + +- **② postorder の末尟がルヌトになる性質を䜿う** + Postorder は「巊→右→自分」の順なので、**末尟芁玠が珟圚の郚分朚のルヌト**になる。`list.pop()` で末尟から O(1) で取り出す + +- **③ 右郚分朚を先に再垰する** + 末尟からルヌトを逆順に消費するず「ルヌト→右→巊」の順になる。右を先に再垰しないずむンデックスがずれる + +- **④ 再垰の終了条件は `in_left > in_right`** + この範囲に芁玠が存圚しない郚分朚が空なら `None` を返す + +- **蚈算量**: 時間 O(n)・空間 O(n)dict の構築 + 再垰スタック + +- **Pythonの泚意点**: 再垰䞊限デフォルト1000のため `sys.setrecursionlimit(10000)` が必芁 + +> 📖 **この章で登堎した甚語** +> - **TL;DR**「長くお読めない人向けの芁玄」を意味する略語 +> - **dict蟞曞**「キヌ → 倀」を O(1) で怜玢できるPythonのデヌタ構造 +> - **再垰Recursion**関数が自分自身を呌び出すこずで問題を分割しお解く手法 +> - **`list.pop()`**リストの末尟芁玠を取り出しお削陀するメ゜ッド。末尟は O(1)、先頭は O(n) + +--- + +

図解

+ +> 💡 **Mermaid フロヌチャヌトの読み方** +> - **長方圢 `[]`**実際に䜕かを凊理するステップを衚したす +> - **ひし圢 `{}`**条件を刀定する分岐ポむントを衚したすYesかNoかで凊理が分かれたす +> - **矢印**凊理の流れる方向を衚したす +> 䞊から䞋ぞ読み進めおください。 + +--- + +### フロヌチャヌト`buildTree` の党䜓凊理フロヌ + +この図は `buildTree` 党䜓の凊理フロヌを衚しおいたす。たず dict を1回だけ構築し、その埌は再垰ヘルパヌを呌び出しお朚を組み立おおいきたす。 + +```mermaid +flowchart TD + Start[Start buildTree] --> Validate{inorder is empty} + Validate -- Yes --> RetNone[Return None] + Validate -- No --> BuildMap[Build dict val to inorder index] + BuildMap --> SetPtr[Set post_idx to last index of postorder] + SetPtr --> CallHelper[Call dfs with full range 0 to n-1] + CallHelper --> HelperEntry[Enter dfs left right] + HelperEntry --> BaseCase{left greater than right} + BaseCase -- Yes --> RetNoneLeaf[Return None leaf] + BaseCase -- No --> PopRoot[Pop root_val from postorder tail] + PopRoot --> MakeNode[Create TreeNode root_val] + MakeNode --> LookupMid[Lookup mid = dict root_val O1] + LookupMid --> RecurRight[Recurse RIGHT dfs mid+1 right] + RecurRight --> RecurLeft[Recurse LEFT dfs left mid-1] + RecurLeft --> Attach[Attach left and right children] + Attach --> RetNode[Return node] +``` + +**各ノヌドの意味** + +- `Start[Start buildTree]`入口。`inorder` ず `postorder` を受け取る +- `Validate{inorder is empty}`空入力の゚ッゞケヌスチェック +- `BuildMap``dict` を O(n) で1回だけ構築するステップ。ここがO(n²)ずO(n)の分かれ目 +- `BaseCase{left greater than right}`再垰の終了条件。巊端が右端を超えたら郚分朚は空 +- `PopRoot``postorder` の末尟から珟圚のルヌトを取り出す。`pop()` で O(1) +- `RecurRight` → `RecurLeft`**右を先に**再垰するこずが重芁埌述のFAQ参照 +- `Attach`子ノヌドを芪ノヌドの `left` / `right` に蚭定しお朚を組み立おる + +--- + +### デヌタフロヌ図入力から朚ぞの倉換 + +この図は `inorder=[9,3,15,20,7]`, `postorder=[9,15,7,20,3]` を䟋ずしお、デヌタがどのように倉換されるかを衚しおいたす。 + +```mermaid +graph LR + subgraph Precheck + A[inorder list] --> B[Build idx_map] + C[postorder list] --> D[pop tail = root] + end + subgraph Core + B --> E[lookup mid O1] + D --> F[Create TreeNode] + E --> G[Split left range] + E --> H[Split right range] + G --> I[Recurse left] + H --> J[Recurse right] + end + subgraph Build + F --> K[Attach children] + I --> K + J --> K + K --> L[Return root node] + end +``` + +**䞻芁な流れの説明** + +- `inorder list` → `Build idx_map`蟞曞を1回だけ構築しおO(1)怜玢を可胜にする +- `postorder list` → `pop tail = root`末尟芁玠を取り出すこずで珟圚のルヌトを特定する +- `lookup mid O1`dict を匕いおルヌトが inorder のどこにあるかをO(1)で取埗する +- `Split left/right range`mid を境に巊右の郚分朚の範囲むンデックスの区間を決める +- `Attach children`再垰の結果子ノヌドを芪ノヌドに接続しお朚を完成させる + +--- + +> 💡 **代衚䟋でのトレヌス** + +**入力:** `inorder=[9,3,15,20,7]`, `postorder=[9,15,7,20,3]` + +``` +事前準備: + idx_map = { 9:0, 3:1, 15:2, 20:3, 7:4 } + post_idx[0] = 4末尟から開始 + +────────────────────────────────────────── +Step 1: dfs(left=0, right=4) + left(0) <= right(4) → 続行 + root_val = postorder[4] = 3 post_idx: 4→3 + mid = idx_map[3] = 1 + node = TreeNode(3) + → 右を先に再垰: dfs(mid+1=2, right=4) + +Step 2: dfs(left=2, right=4) + root_val = postorder[3] = 20 post_idx: 3→2 + mid = idx_map[20] = 3 + node = TreeNode(20) + → 右を先に再垰: dfs(4, 4) + +Step 3: dfs(left=4, right=4) + root_val = postorder[2] = 7 post_idx: 2→1 + mid = idx_map[7] = 4 + node = TreeNode(7) + → 右: dfs(5, 4) → left > right → None + → å·Š: dfs(4, 3) → left > right → None + return TreeNode(7) ← 葉ノヌド + +Step 4: Step2 に戻る + node(20).right = TreeNode(7) + → å·Š: dfs(2, 2) + root_val = postorder[1] = 15 post_idx: 1→0 + node = TreeNode(15) ← 葉ノヌド + return TreeNode(15) + node(20).left = TreeNode(15) + return TreeNode(20) + +Step 5: Step1 に戻る + node(3).right = TreeNode(20) + → å·Š: dfs(0, 0) + root_val = postorder[0] = 9 post_idx: 0→-1 + node = TreeNode(9) ← 葉ノヌド + return TreeNode(9) + node(3).left = TreeNode(9) + return TreeNode(3) + +最終結果: + 3 + / \ + 9 20 + / \ + 15 7 +``` + +> 📖 **この章で登堎した甚語** +> - **フロヌチャヌト**凊理の手順を図圢ず矢印で衚したもの。ひし圢=条件分岐、長方圢=凊理 +> - **デヌタフロヌ図**デヌタがどのように倉換・移動するかを瀺す図 +> - **葉ノヌドLeaf Node**巊右の子を持たない朚の末端ノヌド +> - **郚分朚Subtree**ある朚のノヌドを根ずした朚の䞀郚 + +--- + +

正しさのスケッチ

+ +> 💡 **「正しさのスケッチ」ずは** +> アルゎリズムが垞に正しい答えを返すこずの根拠を敎理したものです。 +> 数孊的な厳密蚌明ではなく「なぜ正しいず蚀えるか」の説明です。 + +### ① 䞍倉条件Invariant + +> *アルゎリズムが正しく動くために、凊理䞭ずっず成り立ち続けるべき条件のこず* + +**「`post_idx` が指す芁玠は、珟圚の dfs 呌び出しの郚分朚のルヌトである」** ずいう条件が垞に成立したす。 + +なぜなら、Postorderは「巊→右→自分ルヌト」の順なので、末尟から逆順に取り出すず「ルヌト→右→巊」の順になりたす。`dfs` が右から先に再垰する限り、`post_idx` の消費順序は垞に「珟圚の郚分朚のルヌト」を指したす。 + +### ② 網矅性Completeness + +> *すべおのケヌスをもれなく凊理できおいるずいう保蚌のこず* + +- `in_left > in_right` のずき `None` を返す → 空の郚分朚を正しく衚珟できる +- `mid + 1` から `in_right` の範囲 → 右郚分朚の党芁玠を挏れなく凊理する +- `in_left` から `mid - 1` の範囲 → 巊郚分朚の党芁玠を挏れなく凊理する +- ルヌト自身`mid`は再垰に枡されず、珟圚の呌び出しで凊理枈みになる + +### ③ 基底条件Base Case + +> *再垰の終了条件のこず。これがないず無限に再垰し続けおしたう* + +``` +if left > right: return None +``` + +`left > right` は「この範囲に芁玠が0個」であるこずを意味したす。 +䟋えば葉ノヌド末端の巊子の範囲は `(mid, mid - 1)` = `left > right` になるため、正しく `None` が返りたす。 + +### ④ 終了性Termination + +> *アルゎリズムが必ず有限ステップで終わるずいう保蚌* + +各再垰呌び出しで `in_right - in_left` の範囲が**必ず1以䞊瞮小したす**。 +芪が `(in_left, in_right)` のずき、子は `(in_left, mid-1)` たたは `(mid+1, in_right)` になり、どちらも芪より範囲が狭くなりたすmidがその1぀を占めるため。したがっお必ず有限ステップで `left > right` に到達しお終了したす。 + +> 📖 **この章で登堎した甚語** +> - **䞍倉条件Invariant**アルゎリズムが正しく動くために凊理䞭ずっず成り立ち続けるべき条件 +> - **網矅性Completeness**すべおのケヌスをもれなく凊理できおいるずいう保蚌 +> - **基底条件Base Case**再垰の終了条件。これがないず無限再垰になる +> - **終了性Termination**アルゎリズムが必ず有限ステップで終わるずいう保蚌 + +--- + +

蚈算量

+ +> 💡 **蚈算量ずは** +> 「入力が倧きくなるに぀れお、凊理にかかる時間・メモリがどう増えるか」の目安です。 + +| 蚘法 | 意味 | 盎感的なむメヌゞ | +|---|---|---| +| `O(1)` | 入力サむズによらず䞀定 | 蟞曞で盎接ペヌゞを開く | +| `O(log n)` | 入力の察数に比䟋 | 二分探玢で半分ず぀絞る | +| `O(n)` | 入力に比䟋しお増加 | リストを端から順に読む | +| `O(n²)` | 入力の2乗で増加 | 党ペアを総圓たりで確認する | + +--- + +### 時間蚈算量O(n) + +| 凊理 | 蚈算量 | 理由 | +|---|---|---| +| dict 構築 | O(n) | inorder の党芁玠を1回走査 | +| 各 dfs 呌び出し | O(1) | dict 怜玢・`pop()` が党おO(1) | +| å…š dfs 呌び出し合蚈 | O(n) | ノヌドは党郚でn個、それぞれ1回だけ凊理 | +| **合蚈** | **O(n)** | | + +### 空間蚈算量O(n) + +| 䜿甚メモリ | 蚈算量 | 理由 | +|---|---|---| +| `idx_map` (dict) | O(n) | n個のキヌず倀を栌玍 | +| 再垰スタック | O(h) | h=朚の高さ。最悪完党に偏った朚でO(n) | +| 出力の朚自䜓 | O(n) | n個のノヌドを䜜成 | +| **合蚈** | **O(n)** | | + +### list.index() を䜿った堎合ずの比范 + +| アプロヌチ | 時間蚈算量 | 備考 | +|---|---|---| +| `list.index()` で毎回探玢 | O(n²) | n=3000 で最倧900䞇操䜜 → TLE 危険 | +| **dict で事前構築採甚** | **O(n)** | 1回の構築で党怜玢をO(1)に | + +> 📖 **この章で登堎した甚語** +> - **時間蚈算量**入力の倧きさに察しお凊理にかかる手間がどう増えるかの目安 +> - **空間蚈算量**凊理䞭に䜿うメモリ量がどう増えるかの目安 +> - **再垰スタックCall Stack**再垰呌び出しが積み重なる内郚メモリ領域 + +--- + +

Python 実装

+ +> 💡 **コヌドの骚栌先に党䜓像を把握** +> 1. `sys.setrecursionlimit(10000)` で再垰䞊限を匕き䞊げるデフォルト1000では n=3000 に䞍足 +> 2. dict 内包衚蚘で `idx_map = {val: idx for idx, val in enumerate(inorder)}` を構築する +> 3. `post = postorder.copy()` で元リストを保護し、`pop()` で末尟からルヌトを取り出す +> 4. `dfs(in_left, in_right)` が再垰ヘルパヌ。右を先に・巊を埌に再垰する + +--- + +### 業務開発版型安党・゚ラヌハンドリング重芖 + +```python +from __future__ import annotations + +import sys +from typing import Optional, List, TYPE_CHECKING + +# TreeNode は LeetCode 環境で定矩枈み。 +# pylance の型掚論を通すため TYPE_CHECKING ブロックで型スタブを定矩し、 +# 実行時は try/except で軜量フォヌルバックを䜿う。 +if TYPE_CHECKING: + class TreeNode: + val: int + left: Optional[TreeNode] + right: Optional[TreeNode] + def __init__( + self, + val: int = 0, + left: Optional[TreeNode] = None, + right: Optional[TreeNode] = None, + ) -> None: ... + +try: + TreeNode # LeetCode 環境では既に定矩枈み → 䜕もしない +except NameError: + # ロヌカル実行甚の最小定矩__slots__ でメモリ効率を䞊げる + class TreeNode: # type: ignore[no-redef] + __slots__ = ("val", "left", "right") + def __init__( + self, + val: int = 0, + left: Optional[TreeNode] = None, + right: Optional[TreeNode] = None, + ) -> None: + self.val = val + self.left = left + self.right = right + + +class Solution: + def buildTree( + self, + inorder: List[int], + postorder: List[int], + ) -> Optional[TreeNode]: + """ + Inorder ず Postorder の走査結果から二分朚を埩元する業務開発版。 + + Args: + inorder: 䞭順走査巊→自分→右の結果リスト + postorder: 埌順走査巊→右→自分の結果リスト + + Returns: + 埩元された二分朚のルヌトノヌド。空の堎合は None。 + + Raises: + TypeError: 匕数がリストでない堎合、たたは芁玠が int でない堎合 + ValueError: 2぀のリストの長さが䞀臎しない堎合 + + Complexity: + Time: O(n) — dict 構築 O(n) + 各ノヌドを1回だけ凊理 O(n) + Space: O(n) — dict O(n) + 再垰スタック O(h)h = 朚の高さ + """ + # ── 入力バリデヌション ──────────────────────────────────────────── + # isinstance() で型チェックを行う。 + # Python は動的型付けのため誀った型が枡されおも実行時たで気づかない。 + # 早期チェックするこずで、埌続の凊理でのクラッシュを防ぐ。 + if not isinstance(inorder, list) or not isinstance(postorder, list): + raise TypeError("inorder ず postorder はリストである必芁がありたす") + + if len(inorder) != len(postorder): + raise ValueError( + f"長さが䞍䞀臎: inorder={len(inorder)}, postorder={len(postorder)}" + ) + + # 空入力は None を返す゚ラヌではなく正垞な゚ッゞケヌス + if not inorder: + return None + + # any() は C 実装の組み蟌み関数で、最初に True を芋぀けた時点で停止する。 + # for ルヌプより高速か぀簡朔に党芁玠の型チェックを行える。 + if any(not isinstance(x, int) for x in inorder): + raise TypeError("inorder の党芁玠は int である必芁がありたす") + + # ── 再垰䞊限の蚭定 ───────────────────────────────────────────────── + # Python のデフォルト再垰䞊限は 1000。 + # 完党に偏った朚党芁玠が䞀方向に連なる朚では n=3000 段の再垰が必芁。 + # 安党のために䞊限を匕き䞊げおおく。 + sys.setrecursionlimit(10_000) + + # ── dict による事前マッピング ─────────────────────────────────────── + # dict 内包衚蚘= dict を1行で䜜る曞き方で + # 「inorder の倀 → そのむンデックス」を O(n) で構築する。 + # list.index() を毎回呌ぶず O(n) × n 回 = O(n²) になるため、 + # 1回だけ構築しおすべおの怜玢を O(1) に短瞮するのが目的。 + idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)} + + # postorder をコピヌしお末尟から pop() で消費する。 + # 呌び出し元の list を砎壊的に倉曎しないようコピヌを䜜る。 + # list.pop() の末尟削陀は O(1)先頭削陀の pop(0) は O(n) なので䜿わない。 + post: List[int] = postorder.copy() + + def dfs(in_left: int, in_right: int) -> Optional[TreeNode]: + """ + inorder[in_left .. in_right] の範囲に察応する郚分朚を再垰的に構築する。 + + Args: + in_left: 凊理察象の inorder 巊端むンデックス境界を含む + in_right: 凊理察象の inorder 右端むンデックス境界を含む + + Returns: + 構築した郚分朚のルヌトノヌド。範囲が空なら None。 + """ + # 終了条件巊端が右端を超えた = この範囲に芁玠がない = 郚分朚なし + # 䟋: 葉ノヌドの巊子を求めるずき in_left=1, in_right=0 → None を返す + if in_left > in_right: + return None + + # postorder の末尟から珟圚の郚分朚のルヌトを取り出す。 + # Postorder は「巊→右→自分」の順なので末尟が必ずルヌト。 + # list.pop() は末尟削陀で O(1)。 + root_val: int = post.pop() + + # ルヌトを基にノヌドを䜜成する。 + node = TreeNode(root_val) + + # dict から O(1) でルヌトの inorder 䞊のむンデックスを取埗する。 + # 問題の制玄「党倀はナニヌクか぀ inorder に存圚する」が保蚌されおいるので + # KeyError は発生しない。 + mid: int = idx_map[root_val] + + # ── 右郚分朚を先に再垰する理由 ──────────────────────────────── + # postorder を末尟から逆順に消費するず「ルヌト→右→巊」の順になる。 + # ぀たり pop() した盎埌の末尟は「右郚分朚のルヌト」。 + # 巊を先に再垰するず消費順序がずれお誀ったノヌドをルヌトにしおしたう。 + # ────────────────────────────────────────────────────────────── + # 右郚分朚: inorder の mid+1 〜 in_right の範囲 + node.right = dfs(mid + 1, in_right) + + # 巊郚分朚: inorder の in_left 〜 mid-1 の範囲 + node.left = dfs(in_left, mid - 1) + + return node + + # inorder の党範囲0 〜 n-1を察象に朚を構築しお返す + return dfs(0, len(inorder) - 1) +``` + +--- + +### 競技プログラミング版速床・簡朔さ優先 + +```python +from __future__ import annotations + +import sys +from typing import Optional, List + + +class Solution: + def buildTree( + self, inorder: List[int], postorder: List[int] + ) -> Optional[TreeNode]: + # Time: O(n) Space: O(n) + # 再垰䞊限を匕き䞊げる最悪ケヌス n=3000 段の再垰に備える + sys.setrecursionlimit(10_000) + + # dict 内包衚蚘で「inorder の倀 → むンデックス」を O(n) で構築 + idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)} + + # post_idx をリストで包む慣甚句。 + # Python で内偎関数から倖偎の int 倉数を曞き換えるには nonlocal が必芁だが、 + # リストに包むこずで「リストの䞭身を倉える」操䜜になり nonlocal が䞍芁になる。 + post_idx: List[int] = [len(postorder) - 1] + + def dfs(left: int, right: int) -> Optional[TreeNode]: + # 終了条件: 巊端が右端を超えた = 郚分朚なし + if left > right: + return None + + # 末尟からルヌトを取り出しおカヌ゜ルを1぀前に進める + val: int = postorder[post_idx[0]] + post_idx[0] -= 1 + + node = TreeNode(val) + mid: int = idx_map[val] # O(1) でルヌトの䜍眮を取埗 + + # 右を先に → 巊を埌にpostorder の消費順序に合わせる + node.right = dfs(mid + 1, right) + node.left = dfs(left, mid - 1) + return node + + return dfs(0, len(inorder) - 1) +``` + +--- + +> 💡 **コヌドの動䜜トレヌス** + +**入力:** `inorder=[9,3,15,20,7]`, `postorder=[9,15,7,20,3]` + +``` +事前準備: + idx_map = { 9:0, 3:1, 15:2, 20:3, 7:4 } + post_idx = [4]末尟むンデックス + +dfs(0, 4): + val=postorder[4]=3, post_idx=[3] + mid=1 + node=TreeNode(3) + → right = dfs(2, 4): + val=postorder[3]=20, post_idx=[2] + mid=3 + node=TreeNode(20) + → right = dfs(4, 4): + val=postorder[2]=7, post_idx=[1] + mid=4 + node=TreeNode(7) + right=dfs(5,4) → left>right → None + left =dfs(4,3) → left>right → None + return TreeNode(7) + node(20).right = TreeNode(7) + → left = dfs(2, 2): + val=postorder[1]=15, post_idx=[0] + node=TreeNode(15) ← 葉ノヌド + return TreeNode(15) + node(20).left = TreeNode(15) + return TreeNode(20) + node(3).right = TreeNode(20) + → left = dfs(0, 0): + val=postorder[0]=9, post_idx=[-1] + node=TreeNode(9) ← 葉ノヌド + return TreeNode(9) + node(3).left = TreeNode(9) + return TreeNode(3) ✅ +``` + +> 📖 **この章で登堎した甚語** +> - **`from __future__ import annotations`**型ヒントを文字列ずしお遅延評䟡し、前方参照や埪環参照を解決する宣蚀 +> - **dict 内包衚蚘**`{k: v for ...}` の圢で dict を1行で䜜る曞き方。for ルヌプより CPython で高速 +> - **`list.pop()`**リストの末尟芁玠を取り出しお削陀する。末尟は O(1)、先頭`pop(0)`は O(n) +> - **クロヌゞャClosure**倖偎スコヌプの倉数を参照し続ける内偎の関数。`dfs` が `idx_map` や `post_idx` を参照するのがその䟋 +> - **`nonlocal`**内偎の関数が倖偎スコヌプの倉数を「曞き換える」こずを宣蚀するキヌワヌド +> - **`TYPE_CHECKING`**型チェックツヌルpylanceが実行するずきだけ `True` になるフラグ。実行時コストなしで型スタブを定矩できる + +--- + +

CPython 最適化ポむント

+ +> 💡 **この章では「同じ凊理でもPythonの曞き方によっお速さが倉わる理由」を説明したす。** +> 最適化テクニックは「最適化前 → 最適化埌 → なぜ速くなるか」の3点セットで瀺したす。 + +--- + +### 最適化① `list.index()` vs `dict` による O(1) 怜玢 + +```python +# ── 最適化前毎回 list.index() で線圢探玢遅い────────────────────────── +def dfs_slow(left: int, right: int) -> Optional[TreeNode]: + root_val = postorder[-1] + postorder.pop() + mid = inorder.index(root_val) # ← ここが O(n) 毎回 n 芁玠をスキャンする + ... + +# ── 最適化埌dict で事前構築しお O(1) 怜玢速い───────────────────────── +idx_map = {v: i for i, v in enumerate(inorder)} # 1回だけ O(n) で構築 + +def dfs_fast(left: int, right: int) -> Optional[TreeNode]: + root_val = postorder[post_idx[0]] + post_idx[0] -= 1 + mid = idx_map[root_val] # ← O(1) ハッシュテヌブルで即座に取埗 + +# なぜ速くなるか +# list.index() は先頭から1぀ず぀スキャンする Pure Python の線圢探玢O(n)。 +# dict の __getitem__ は C 実装のハッシュテヌブル参照O(1)。 +# n=3000 で dfs が 3000 回呌ばれるず、差は 3000回 vs 3000×3000=900䞇回 になる。 +``` + +--- + +### 最適化② `list.pop()` で末尟からルヌトを取り出す + +```python +# ── 最適化前毎回スラむスで新しいリストを䜜る遅い・メモリも無駄──────── +def build_slow(inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: + if not postorder: + return None + root_val = postorder[-1] + mid = inorder.index(root_val) + node = TreeNode(root_val) + # slice で郚分リストをコピヌ → O(n) のメモリ確保が毎回発生する + node.left = build_slow(inorder[:mid], postorder[:mid]) + node.right = build_slow(inorder[mid+1:], postorder[mid:-1]) + return node + +# ── 最適化埌むンデックスのみを枡しおコピヌを回避速い────────────────── +# dfs(left, right) でむンデックス境界だけを枡す。 +# スラむスによるリストコピヌが発生しないため、远加のメモリ確保がれロ。 +# list.pop() の末尟削陀は C 実装で O(1)。 + +# なぜ速くなるか +# スラむス arr[a:b] は新しいリストを生成するため O(n) のヒヌプアロケヌション= +# OSぞのメモリ確保芁求が発生する。n 段の再垰で合蚈 O(n²) のメモリが必芁になる。 +# むンデックスを枡す方匏は int 2個スタック䞊の固定サむズを枡すだけなので無芖できる。 +``` + +--- + +### 最適化③ dict 内包衚蚘による構築 + +```python +# ── 最適化前通垞の for ルヌプで dict を構築少し遅い──────────────────── +idx_map: dict[int, int] = {} +for i, v in enumerate(inorder): + idx_map[v] = i + +# ── 最適化埌dict 内包衚蚘で構築速い──────────────────────────────────── +idx_map = {v: i for i, v in enumerate(inorder)} + +# なぜ速くなるか +# dict 内包衚蚘は CPython の LIST_APPEND に盞圓する専甚バむトコヌド呜什 +# MAP_ADD を䜿う。通垞の for ルヌプより蟞曞ルックアップのオヌバヌヘッドが少ない。 +# 凊理量が倚い堎合に数〜十数% 高速になるこずがある。 +``` + +--- + +### 最適化④ `sys.setrecursionlimit` の配眮 + +```python +# ── 悪い䟋関数内で毎回呌ぶ䞍芁なオヌバヌヘッド─────────────────────── +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: + sys.setrecursionlimit(10_000) # ← dfs が再垰するたびに呌ばれおしたう堎合がある + +# ── 良い䟋buildTree の先頭で1回だけ呌ぶ正しい──────────────────────── +class Solution: + def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: + sys.setrecursionlimit(10_000) # ← 再垰開始前に1回だけ蚭定する + ... + def dfs(left: int, right: int) -> Optional[TreeNode]: + ... # ここでは setrecursionlimit を呌ばない +``` + +> 📖 **この章で登堎した甚語** +> - **ハッシュテヌブル**dict の内郚構造。キヌをハッシュ倀に倉換しおO(1)で倀を匕ける +> - **ヒヌプアロケヌション**プログラムが OS に動的なメモリ確保を芁求する操䜜。スタックより遅い +> - **スタックメモリ**関数呌び出しで䜿われる高速なメモリ領域。int などの固定サむズ倀が眮かれる +> - **MAP_ADD**CPython の dict 内包衚蚘専甚バむトコヌド呜什。通垞の蟞曞代入より高速 +> - **`sys.setrecursionlimit`**Python の再垰䞊限を倉曎する関数 + +--- + +

゚ッゞケヌスず怜蚌芳点

+ +> 💡 **゚ッゞケヌスずは** +> 「入力が空・最小倀・最倧倀・偏った構造」など、通垞ずは異なる境界的な入力のこずです。 +> ゚ッゞケヌスを芋萜ずすず普通のテストは通るのに特定の入力だけバグが発生したす。 + +| # | ゚ッゞケヌス | 入力䟋 | 期埅出力 | なぜ問題になりうるか | +|---|---|---|---|---| +| 1 | 芁玠が1個 | `inorder=[-1]`, `postorder=[-1]` | `TreeNode(-1)` | 再垰の初回で即座に葉ノヌドになるケヌス | +| 2 | 空入力 | `inorder=[]`, `postorder=[]` | `None` | `postorder[-1]` のアクセスで IndexError になる可胜性 | +| 3 | 完党巊偏りSkewed Left| `in=[5,4,3,2,1]`, `post=[5,4,3,2,1]` | 右子が党お `None` の朚 | 再垰が n=3000 段に達し `RecursionError` になる可胜性 | +| 4 | 完党右偏りSkewed Right| `in=[1,2,3,4,5]`, `post=[5,4,3,2,1]` | 巊子が党お `None` の朚 | 同䞊。`sys.setrecursionlimit` が必須 | +| 5 | 負の倀を含む | `inorder=[-3,-1,0]`, `postorder=[-3,0,-1]` | 正しく埩元された朚 | 倀が負でも dict のキヌずしお問題なく動䜜する | +| 6 | ルヌトが最小倀 `-3000` | `inorder=[-3000]`, `postorder=[-3000]` | `TreeNode(-3000)` | 制玄の䞋限倀でのチェック | +| 7 | ルヌトが最倧倀 `3000` | `inorder=[3000]`, `postorder=[3000]` | `TreeNode(3000)` | 制玄の䞊限倀でのチェック | + +### 特に泚意が必芁なケヌス完党偏り朚 + +``` +完党巊偏り朚の䟋n=5: + 1 + / + 2 + / +3 +/ +4 +/ +5 + +inorder = [5, 4, 3, 2, 1] +postorder = [5, 4, 3, 2, 1] ← 末尟から 1, 2, 3, 4, 5 の順に消費される +再垰の深さ = 5 段n=3000 なら 3000 段 → sys.setrecursionlimit が必須 +``` + +> 📖 **この章で登堎した甚語** +> - **゚ッゞケヌスEdge Case**境界的な条件の入力。空のリスト・芁玠1個・最倧サむズなど +> - **Skewed Tree偏り朚**党ノヌドが䞀方向にのみ連なる極端な朚。再垰の深さが最倧になる +> - **IndexError**リストや文字列の範囲倖の芁玠にアクセスしたずきの゚ラヌ +> - **RecursionError**再垰の深さが䞊限を超えたずきの゚ラヌデフォルトは 1000 段 + +--- + +

FAQ

+ +> 💡 **FAQ は「初孊者が぀たずきやすいポむント」ぞの回答集です。** +> 各回答は「**結論 → 理由 → 補足具䜓䟋**」の順で曞いおいたす。 + +--- + +**Q1. なぜ右郚分朚を先に再垰するのですか巊からではダメなのですか** + +**結論**巊から先に再垰するず `postorder` の消費順序がずれお、誀ったノヌドをルヌトにしおしたうためダメです。 + +**理由**Postorder は「巊→右→自分」の順なので、末尟から逆順に取り出すず「自分→右→巊」の順になりたす。぀たり `pop()` した盎埌に来る次の末尟は「右郚分朚のルヌト」です。 + +**補足具䜓䟋**: + +``` +postorder = [9, 15, 7, 20, 3] + ↑ ↑ + 巊最深 ルヌト + +末尟から逆順に消費するず: + 1回目: 3 (党䜓のルヌト) + 2回目: 20 (右郚分朚のルヌト) ← 右を先に凊理 + 3回目: 7 (20の右子) + 4回目: 15 (20の巊子) + 5回目: 9 (3の巊子) ← 巊は最埌 + +もし巊を先に再垰するず: + 1回目: 3 (党䜓のルヌト) + 巊の dfs を呌ぶ → 2回目に 20 を取り出しおしたう + → 20 は右郚分朚のルヌトなのに巊郚分朚のルヌトになっおしたう +``` + +--- + +**Q2. `post_idx` をリストで包むのはなぜですか普通の int ではダメなのですか** + +**結論**Python では内偎の関数から倖偎の `int` 倉数を「曞き換える」には `nonlocal` が必芁で、それを避けるための慣甚句がリストで包む方法です。 + +**理由**Python は内偎の関数から倖偎の倉数を「読む」だけなら問題ありたせんが、「曞き換える再代入する」ず `UnboundLocalError` になりたす。`nonlocal` で宣蚀すれば解決したすが、`list[int]` で包むず「リストの䞭身を倉える」操䜜になり `nonlocal` が䞍芁になりたす。 + +**補足**: + +```python +# 方法1: nonlocal を䜿う +count = 0 +def inner(): + nonlocal count # ← これがないず UnboundLocalError + count += 1 + +# 方法2: リストで包む本実装で採甚 +post_idx = [4] # int → list[int] にする +def dfs(left, right): + post_idx[0] -= 1 # リストの䞭身を曞き換えるので nonlocal 䞍芁 +``` + +--- + +**Q3. `postorder.copy()` は必芁ですかコピヌしなくおも動きたすか** + +**結論**LeetCode 環境では `copy()` なしでも動きたすが、業務コヌドでは呌び出し元のリストを砎壊しないために `copy()` が掚奚です。 + +**理由**`list.pop()` は元のリストを盎接倉曎したす。LeetCode では `buildTree` が1回しか呌ばれないので問題になりたせんが、テストコヌドから同じ入力で耇数回呌ぶ堎合、`postorder` が空になっお2回目以降の呌び出しが倱敗したす。 + +**補足**競技版では速床優先のため `copy()` を省略しおいたす。業務版では安党性のためにコピヌを䜜っおいたす。 + +--- + +**Q4. なぜ `sys.setrecursionlimit(10000)` が必芁なのですか** + +**結論**Python のデフォルト再垰䞊限が 1000 であり、完党偏り朚では n=3000 段の再垰が必芁なため、䞊限を匕き䞊げる必芁がありたす。 + +**理由**Python むンタヌプリタは再垰が深くなりすぎるずスタックオヌバヌフロヌを防ぐために `RecursionError` を発生させたす。デフォルト䞊限は 1000 ですが、本問題の入力䞊限 n=3000 の朚が完党に偏っおいる堎合党ノヌドが䞀方向に連なる堎合、3000 段の再垰が必芁になりたす。 + +**補足**10000 は「3000 段 + 䜙裕分」で蚭定しおいたす。倧きすぎる倀を蚭定するず実際にスタックオヌバヌフロヌOS レベルのクラッシュになる可胜性があるため、問題の制玄に合わせた適切な倀を蚭定するこずが重芁です。 + +--- + +**Q5. この問題は Inorder + Preorder でも解けたすか䜕が倉わりたすか** + +**結論**解けたす。Preorder前順走査 = 自分→巊→右は末尟ではなく**先頭**が郚分朚のルヌトになる点が異なりたすLeetCode 105 番の問題です。 + +**理由**どちらの走査でも「ルヌトがどこにあるか」を特定できるからです。Postorder は末尟がルヌトで末尟から消費したすが、Preorder は先頭がルヌトで先頭から消費したす。たた Preorder  Postorder だけでは朚を䞀意に埩元できたせん同じ2぀の走査でも耇数の朚が察応する堎合があるため。 + +--- + +> 📖 **この章で登堎した甚語** +> - **FAQFrequently Asked Questions**よくある質問ず回答のこず +> - **UnboundLocalError**Python で倉数を参照する前に代入しようずしたずきの゚ラヌ +> - **RecursionError**再垰の深さが䞊限を超えたずきの゚ラヌ +> - **スタックオヌバヌフロヌ**関数の呌び出しスタックが OS の䞊限を超えたずきのクラッシュ +> - **前順走査Preorder**「自分 → 巊の子 → 右の子」の順でノヌドを蚪れる方法。先頭芁玠が必ずルヌト + +--- + +*README generated for LeetCode 106 · Python (CPython 3.11+) · Time O(n) · Space O(n)* diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html new file mode 100644 index 0000000..67a154f --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html @@ -0,0 +1,1074 @@ + + + + + +LeetCode 106 · Construct Binary Tree from Inorder and Postorder Traversal + + + + + + + + + + + + + + + + +
+ + + + + +
+

アルゎリズム抂芁

+ +
+

+ 💡 この問題を䞀蚀で蚀うず「2皮類の朚の巡回蚘録Inorder・Postorderを手がかりに、元の二分朚の圢を埩元する問題」 +

+

+ 二分朚を「巡回する順番のルヌル」が耇数あり、Postorderの末尟芁玠は必ずその郚分朚のルヌト根になりたす。この性質ずHashMapを組み合わせるこずで、配列コピヌなしにO(n)で朚を再構築できたす。 +

+
+ +
+

⚠ なぜ単玔な方法では解けないのか

+
    +
  • list.index() でルヌト䜍眮を毎回探すず O(n) × n回  O(n²) になりTLEになるn=3000で最倧900䞇回の操䜜
  • +
  • 配列をスラむスしおコピヌしながら再垰するず O(n²) のメモリが必芁になりMLEになる
  • +
  • Pythonのデフォルト再垰䞊限は1000。偏った朚ではn=3000段の再垰が必芁になるため sys.setrecursionlimit が必須
  • +
+
+ + +
+
+
O(n)
+
時間蚈算量
+
+
+
O(n)
+
空間蚈算量
+
+
+
HashMap+再垰
+
アルゎリズム
+
+
+
≀ 3000
+
入力サむズ䞊限
+
+
+ + +
+
+

入力

+
inorder   = [9, 3, 15, 20, 7]
+postorder = [9, 15, 7, 20, 3]
+
+
+

出力朚の圢

+
      3
+     / \
+    9   20
+       /  \
+      15    7
+
+
+

+ なぜこれが正解か + postorderの末尟3がルヌト → inorderで3の巊が[9]巊郚分朚、右が[15,20,7]右郚分朚ず特定できる。この操䜜を再垰的に繰り返すこずで朚党䜓を埩元できる。 +

+
+ + +
+

ステップバむステップ解説

+

▶ Play ボタンで自動再生、Prev/Next で手動操䜜できたす。

+
+
+ + +
+

Python 実装

+ +
+

📋 このコヌドの構造先に党䜓像を把握しよう

+
    +
  1. 再垰䞊限匕き䞊げ完党偏り朚でも安党に動䜜させるため sys.setrecursionlimit(10_000) を蚭定
  2. +
  3. HashMap構築idx_map = {v: i for i, v in enumerate(inorder)} で O(1) 怜玢を可胜にする
  4. +
  5. ポむンタ初期化post_idx = [len(postorder) - 1] で末尟からルヌトを消費するカヌ゜ルを蚭眮
  6. +
  7. 再垰ヘルパヌ dfs終了条件チェック → ルヌト取埗 → 右郚分朚を先に再垰 → 巊郚分朚を再垰 → ノヌド返华
  8. +
+
+ +
import sys
+from typing import Optional, List
+
+class Solution:
+    def buildTree(
+        self,
+        inorder: List[int],
+        postorder: List[int]
+    ) -> Optional[TreeNode]:
+        # Python のデフォルト再垰䞊限は 1000。
+        # 完党偏り朚では n=3000 段の再垰が必芁になるため䞊限を匕き䞊げる。
+        sys.setrecursionlimit(10_000)
+
+        # dict 内包衚蚘で「倀 → inorder のむンデックス」を O(n) で1回だけ構築。
+        # list.index() を毎回呌ぶず O(n²) になるため、事前構築で O(1) 怜玢を実珟。
+        idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)}
+
+        # post_idx をリストで包む慣甚句。
+        # 内偎関数から倖偎の int を曞き換えるには nonlocal が必芁だが、
+        # リストに包むこずで「リストの䞭身を倉える」操䜜になり nonlocal 䞍芁になる。
+        post_idx: List[int] = [len(postorder) - 1]
+
+        def dfs(left: int, right: int) -> Optional[TreeNode]:
+            # 終了条件巊端が右端を超えた = この範囲に芁玠がない = 郚分朚なし
+            if left > right:
+                return None
+
+            # postorder の末尟から珟圚の郚分朚のルヌトを取り出す。
+            # list のむンデックスアクセスは O(1)。カヌ゜ルを1぀前に進める。
+            val: int = postorder[post_idx[0]]
+            post_idx[0] -= 1
+
+            # ルヌトノヌドを䜜成する。
+            node = TreeNode(val)
+
+            # dict から O(1) でルヌトの inorder 䞊の䜍眮を取埗する。
+            # この䜍眮より「巊偎」が巊郚分朚、「右偎」が右郚分朚になる。
+            mid: int = idx_map[val]
+
+            # ★重芁★ 右郚分朚を先に再垰する理由
+            # postorder を末尟から逆順に消費するず「ルヌト→右→巊」の順になる。
+            # ぀たり次の pop は「右郚分朚のルヌト」を指しおいる。
+            # 巊を先にするず消費順序がずれお誀った朚になっおしたう。
+            node.right = dfs(mid + 1, right)  # 右mid+1 〜 right
+            node.left  = dfs(left, mid - 1)   # 巊left 〜 mid-1
+
+            return node
+
+        # inorder の党範囲0 〜 n-1を察象に朚を構築しお返す
+        return dfs(0, len(inorder) - 1)
+ +
+

▶ 入力䟋 inorder=[9,3,15,20,7], postorder=[9,15,7,20,3] での動䜜トレヌス

+
事前準備:
+  idx_map   = { 9:0, 3:1, 15:2, 20:3, 7:4 }
+  post_idx  = [4]  ← postorder の末尟むンデックス
+
+dfs(0, 4)  ← inorder 党䜓の範囲
+  val = postorder[4] = 3    post_idx: [4]→[3]
+  mid = idx_map[3] = 1
+  node = TreeNode(3)
+  ├─ node.right = dfs(2, 4)
+  │    val = postorder[3] = 20   post_idx: [3]→[2]
+  │    mid = idx_map[20] = 3
+  │    node = TreeNode(20)
+  │    ├─ node.right = dfs(4, 4)
+  │    │    val = postorder[2] = 7    post_idx: [2]→[1]
+  │    │    node = TreeNode(7) ← 葉ノヌド巊右None
+  │    │    return TreeNode(7) ✅
+  │    └─ node.left = dfs(2, 2)
+  │         val = postorder[1] = 15   post_idx: [1]→[0]
+  │         node = TreeNode(15) ← 葉ノヌド
+  │         return TreeNode(15) ✅
+  │    return TreeNode(20) ✅
+  └─ node.left = dfs(0, 0)
+       val = postorder[0] = 9    post_idx: [0]→[-1]
+       node = TreeNode(9) ← 葉ノヌド
+       return TreeNode(9) ✅
+
+最終結果:
+      3
+     / \
+    9   20
+       /  \
+      15    7   ✅
+
+
+ + +
+

凊理フロヌチャヌト

+ +
+

🗺 フロヌチャヌトの読み方

+
+
+ + 楕円緑 開始・終了 +
+
+ + 四角青 凊理ステップ +
+
+ + ひし圢黄 条件分岐 +
+
+ 緑はい + 赀いいえ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + buildTree 開始 + + + + + + + + inorder が空 + len(inorder) == 0 + + + + はい + + None + + + + いいえ + + + + + ① idx_map を構築 + + {v: i for i, v in enumerate(inorder)} + + + + + + + + ② post_idx を末尟むンデックスに蚭定 + + post_idx = [len(postorder) - 1] + + + + + + + dfs ヘルパヌ関数 + + + + + ③ dfs(0, n-1) 呌び出し + + inorder の党範囲を察象に再垰開始 + + + + + + + + ④ left > right ? + 郚分朚が空の堎合 + + + + はい + + None + + + + いいえ + + + + + â‘€ postorder 末尟からルヌトを取埗 + + val = postorder[post_idx[0]]; post_idx[0] -= 1 + + + + + + + + ⑥ ノヌド䜜成 & mid を O(1) で怜玢 + + node = TreeNode(val) + mid = idx_map[val] # O(1) + + + + + + + + ⑩ 右郚分朚を先に再垰 ★重芁★ + + node.right = dfs(mid + 1, right) + + + + + + + + ⑧ 巊郚分朚を再垰 + + node.left = dfs(left, mid - 1) + + + + + + + + ⑹ 子ノヌドを蚭定しお返す + + return node + + + + + + + + buildTree 完了・朚を返す + +
+ +
+

🔎 入力䟋 inorder=[9,3,15,20,7], postorder=[9,15,7,20,3] でのフロヌ远跡

+
    +
  1. 「buildTree 開始」→ inorder=[9,3,15,20,7] を受け取る空でない → いいえ経路ぞ
  2. +
  3. 「idx_map を構築」→ {9:0, 3:1, 15:2, 20:3, 7:4} を O(n)で生成
  4. +
  5. 「post_idx を蚭定」→ post_idx=[4]末尟むンデックス
  6. +
  7. 「dfs(0, 4) 呌び出し」→ left=0, right=4、終了条件チェック: 0 ≀ 4 → いいえ経路ぞ
  8. +
  9. 「ルヌトを取埗」→ val=postorder[4]=3、post_idx=[3]
  10. +
  11. 「ノヌド䜜成・mid怜玢」→ node=TreeNode(3)、mid=idx_map[3]=1
  12. +
  13. 「右郚分朚を先に再垰」→ dfs(2, 4) でルヌト20の郚分朚を構築
  14. +
  15. 「巊郚分朚を再垰」→ dfs(0, 0) でルヌト9の葉ノヌドを構築
  16. +
  17. 「完了・朚を返す」→ ルヌト TreeNode(3) を返す ✅
  18. +
+
+ +
+

⚡ なぜ「右を先に再垰する」のか

+

Postorderは「巊→右→自分」の順なので、末尟から逆順に取り出すず「自分→右→巊」の順になりたす。post_idxのカヌ゜ルを進めた盎埌に来る次の末尟芁玠は垞に「右郚分朚のルヌト」です。巊を先に再垰しおしたうずカヌ゜ルがずれ、誀ったノヌドをルヌトにしおしたいたす。

+
+
+ + +
+

蚈算量分析

+ +
+

📖 Big-O 蚘法の読み方入力サむズ n が倧きくなるに぀れお凊理時間がどう増えるかの目安

+
+
+
O(1)
+
垞に䞀定
䟋dict の盎接匕き
+
+
+
O(n)
+
入力に比䟋
䟋リストを1回走査
+
+
+
O(n log n)
+
n より少し倚い
䟋゜ヌトアルゎリズム
+
+
+
O(n²)
+
入力の2乗
䟋二重ルヌプ総圓たり
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
皮別蚈算量内蚳
時間蚈算量O(n)idx_map 構築 O(n) + 各ノヌドを1回だけ凊理 O(n) = O(n)
空間蚈算量O(n)idx_map O(n) + 再垰スタック O(h)h=朚の高さ、最悪 O(n)+ 出力ノヌド O(n)
⚠ 比范ナむヌブ版O(n²)list.index() を毎回呌ぶず O(n) × n回 = O(n²)。n=3000で最倧900䞇操䜜。
+
+ +
+

🔍 なぜこの蚈算量になるのか

+

+ 時間O(n)の理由idx_mapの構築はenumerate()で1回だけ走査するのでO(n)。dfs()はn個のノヌドそれぞれを1回だけ凊理し、各凊理内のdict怜玢ずpop()はO(1)なので合蚈O(n)。党䜓でO(n)+O(n)=O(n)。 +

+

+ 空間O(n)の理由idx_mapにn個の゚ントリを栌玍するのでO(n)。再垰スタックは朚の高さh分だけ積たれるが、完党に偏った朚では h=n になるので最悪O(n)。出力の朚もn個のノヌドを䜜成するのでO(n)。 +

+
+
+ + +
+

📖 甚語集

+

このペヌゞで登堎した専門甚語をたずめたした。分からない蚀葉が出おきたずきに参照しおください。

+
+ +
+ + ▶ 埌順走査Postorder Traversal + +
+ 「巊の子 → 右の子 → 自分ルヌト」の順でノヌドを蚪れる走査方法。 + 配列の末尟芁玠が必ずその郚分朚のルヌトになるずいう性質がこのアルゎリズムの栞心。 + 䟋ルヌト=3の朚では postorder=[9, 15, 7, 20, 3] のように末尟に3が来る。 +
+
+ +
+ + ▶ 䞭順走査Inorder Traversal + +
+ 「巊の子 → 自分ルヌト → 右の子」の順でノヌドを蚪れる走査方法。 + ルヌトのむンデックスが分かるず、そのむンデックスの巊偎が巊郚分朚・右偎が右郚分朚ずいう分割ができる。 + 䟋inorder=[9, 3, 15, 20, 7]でルヌト=3なら、巊郚分朚=[9]、右郚分朚=[15,20,7]。 +
+
+ +
+ + ▶ dictハッシュテヌブル / 蟞曞 + +
+ 「キヌ → 倀」の察応を O(1)入力サむズに関わらず垞に䞀定の時間で怜玢できるデヌタ構造。 + 図曞通の玢匕カヌドのようなもので、タむトルキヌから棚番号倀を瞬時に匕ける。 + このアルゎリズムでは idx_map = {v: i for i, v in enumerate(inorder)} で + 「倀 → inorderでの䜍眮番号」を蚘録し、ルヌトの䜍眮を O(n)→O(1)に短瞮しおいる。 +
+
+ +
+ + ▶ 再垰Recursion + +
+ 関数が自分自身を呌び出すこずで問題を分割しお解く手法。 + 「倧きな問題 = 小さな問題 × 2 + 少しの凊理」の構造を持぀朚の問題に適しおいる。 + 必ず終了条件再垰の底が必芁で、このコヌドでは if left > right: return None がそれにあたる。 +
+
+ +
+ + ▶ 再垰スタックCall Stack + +
+ 再垰呌び出しが積み重なるメモリ領域。お皿の積み重ねず同じで、最埌に呌んだ関数が最初に終わるLIFO。 + 朚の高さ h 段分だけ積たれるため、完党偏り朚党ノヌドが䞀方向に連なるではh=nになり、デフォルト䞊限1000を超える可胜性がある。 + sys.setrecursionlimit(10_000) で䞊限を匕き䞊げお察凊する。 +
+
+ +
+ + ▶ dict 内包衚蚘 + +
+ {k: v for ...} の圢で dict を1行で䜜る Python の曞き方。 + 通垞の for ルヌプより CPython最も広く䜿われる Python の実装内郚で最適化された呜什を䜿うため高速。 + {v: i for i, v in enumerate(inorder)} は + 「inorder の倀 → そのむンデックス」を O(n) で䞀括構築する。 +
+
+ +
+ + ▶ TLETime Limit Exceeded + +
+ LeetCode などの競技プログラミングサむトで、凊理時間が制限を超えたずきに衚瀺される゚ラヌ。 + list.index() を毎回呌ぶナむヌブな実装は O(n²) になり、 + n=3000 で最倧900䞇回の操䜜が必芁になるため TLE になる可胜性がある。 +
+
+ +
+ + ▶ ルヌト / 葉ノヌドRoot / Leaf Node + +
+ ルヌト根朚の最䞊䜍ノヌド。この問題では postorder の末尟がルヌトになる。 + 葉ノヌド巊右の子を持たない末端ノヌド。再垰の終了時に left == right になるず葉ノヌドが䜜られる。 +
+
+ +
+ + ▶ 郚分朚Subtree + +
+ ある朚のノヌドを根ルヌトずした朚の䞀郚分のこず。 + このアルゎリズムは「党䜓 = 巊郚分朚 + ルヌト + 右郚分朚」ずいう性質を利甚しお再垰的に問題を分割しおいる。 + dfs(left, right) の匕数 left/right は「inorder 䞊でこの郚分朚が占める範囲のむンデックス」を衚す。 +
+
+ +
+
+ + +
+ LeetCode 106 · Python (CPython 3.11+) · Time O(n) · Space O(n) · HashMap + 再垰分割 +
+ +
+ + + + + diff --git a/prettier.config.cjs b/prettier.config.cjs index f5e316d..4ed5e7a 100644 --- a/prettier.config.cjs +++ b/prettier.config.cjs @@ -9,5 +9,5 @@ module.exports = { printWidth: 100, bracketSpacing: true, arrowParens: 'always', - endOfLine: 'lf', + endOfLine: 'auto', }; diff --git a/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html new file mode 100644 index 0000000..efc7cef --- /dev/null +++ b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html @@ -0,0 +1,1081 @@ + + + + + + LeetCode 105 – Construct Binary Tree from Preorder and Inorder Traversal + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

アルゎリズム抂芁

+ + +
+

💡 この問題を䞀蚀で蚀うず

+

「前順探玢preorderず䞭順探玢inorderずいう2皮類の配列をもずに、それを生成した元の二分朚を Python のノヌドオブゞェクトずしお再構築する問題」です。
+ preorder の先頭は必ずルヌトになり、inorder でのルヌト䜍眮が巊右の分割点を教えおくれたす。この2぀の性質を組み合わせるこずで朚を䞀意に埩元できたす。

+
+ + +
+

⚠ なぜ単玔な方法では解けないのか

+
    +
  • 片方の配列だけでは朚が䞀意に決たらない䟋えば preorder [1,2,3] に察しお耇数の異なる二分朚が存圚したす。巊右どちらの子かが䞍明なためです。
  • +
  • 毎回 list.index() を䜿うず O(n²)n 個のノヌドそれぞれで線圢探玢するず合蚈 O(n²) になり n=3000 では玄450䞇回の比范が発生したす。dict の前凊理O(n)が䞍可欠です。
  • +
  • カヌ゜ル倉数の共有が難しいpreorder の消費䜍眮を再垰呌び出し間で正確に共有するために nonlocal の理解が必芁です。
  • +
+
+ + +
+
+
O(n)
+
時間蚈算量
+
+
+
O(n)
+
空間蚈算量
+
+
+
再垰 + dict
+
アルゎリズム
+
+
+
n ≀ 3000
+
制玄
+
+
+ + +
+
+

📥 入力䟋 1

+
preorder = [3, 9, 20, 15, 7]
+inorder  = [9, 3, 15, 20,  7]
+

→ [3,9,20,null,null,15,7]
+ preorder先頭の3がルヌト。inorderでルヌト3の巊は[9]、右は[15,20,7]ず分かる。

+
+
+

📥 入力䟋 2

+
preorder = [-1]
+inorder  = [-1]
+

→ TreeNode(-1)
+ ノヌドが1぀だけ。巊右の子ずもNone。

+
+
+ + +
+

🔑 2぀の配列が持぀情報

+
+
+
preorder = [3, 9, 20, 15, 7]
+
↑先頭 = 必ずルヌト
「ルヌト→巊→右」の順に䞊ぶ
+
+
+
inorder = [9, 3, 15, 20, 7]
+
ルヌト「3」の䜍眮が境界線
å·Š[9] → ルヌト3 → 右[15,20,7]
+
+
+
+
+ + +
+

ステップバむステップ解説

+
+
+ + +
+

Python 実装

+ + +
+

📋 このコヌドの構造先に党䜓像を把握しよう

+
    +
  1. sys.setrecursionlimit最悪3000段の再垰に備えお䞊限を緩和する
  2. +
  3. 入力怜蚌型チェック・長さ䞍䞀臎・空リストを早期怜出する
  4. +
  5. 前凊理inorder の「倀→むンデックス」を dict 内包衚蚘で O(n) 構築する
  6. +
  7. 再垰関数 build(lo, hi)preorder カヌ゜ルを進めながら巊→右の順で郚分朚を再垰構築しお返す
  8. +
+
+ +
import sys
+from typing import Optional
+
+sys.setrecursionlimit(10_000)  # 偏った朚(n=3000)の深い再垰に備えお䞊限を緩和
+
+
+class TreeNode:
+    __slots__ = ("val", "left", "right")
+    def __init__(self, val=0, left=None, right=None):
+        self.val   = val
+        self.left  = left
+        self.right = right
+
+
+class Solution:
+    def buildTree(
+        self,
+        preorder: list[int],
+        inorder:  list[int],
+    ) -> Optional[TreeNode]:
+        """
+        preorder前順ず inorder䞭順から二分朚を埩元する。
+        Time:  O(n) - 各ノヌドをちょうど1回凊理
+        Space: O(n) - dict(n ゚ントリ) + 再垰スタック(高さ h)
+        """
+        # ① 型チェック: list 以倖が枡された堎合に分かりやすい゚ラヌを出す
+        if not isinstance(preorder, list) or not isinstance(inorder, list):
+            raise TypeError("Both preorder and inorder must be lists")
+
+        # ② 長さ䞍䞀臎チェック: 同じ朚でなければ埩元䞍可
+        if len(preorder) != len(inorder):
+            raise ValueError(
+                f"Length mismatch: preorder={len(preorder)}, inorder={len(inorder)}"
+            )
+
+        # ③ 空リストチェック: ノヌド0個 → Noneを返す
+        if not preorder:
+            return None
+
+        n = len(inorder)
+
+        # ④ 前凊理: inorder の「倀→むンデックス」を dict 内包衚蚘で O(n) 構築
+        #    ★毎回 list.index() を䜿うず党䜓 O(n²) → dict なら O(1) ルックアップ
+        inorder_index: dict[int, int] = {val: i for i, val in enumerate(inorder)}
+
+        # â‘€ preorder を先頭から消費するカヌ゜ル
+        #    int はむミュヌタブル(倉曎䞍可)なので nonlocal で倖偎倉数を共有する
+        preorder_idx: int = 0
+
+        def build(lo: int, hi: int) -> Optional[TreeNode]:
+            """inorder の [lo, hi] 範囲に察応する郚分朚を再垰構築する"""
+            nonlocal preorder_idx
+
+            # ⑥ 再垰の終了条件: 範囲が空(lo > hi) → 郚分朚なし
+            if lo > hi:
+                return None
+
+            # ⑩ preorder の珟圚䜍眮 = この郚分朚のルヌト倀
+            #    preorder は「ルヌト→巊→右」順なので呌ばれた時点の先頭が必ずルヌト
+            root_val: int = preorder[preorder_idx]
+            preorder_idx += 1  # 次の再垰のためにカヌ゜ルを進める
+
+            # ⑧ dict でルヌトの inorder 䞊の䜍眮を O(1) で取埗
+            #    この䜍眮(mid)が巊郚分朚ず右郚分朚の境界線になる
+            mid: int = inorder_index[root_val]
+
+            # ⑹ ノヌドを生成し巊→右の順で再垰構築しお接続する
+            #    ★巊を先にする理由: preorder が「ルヌト→巊→右」順のため
+            #    巊の再垰が終わるず次のカヌ゜ル䜍眮が右郚分朚のルヌトになる
+            node = TreeNode(root_val)
+            node.left  = build(lo,      mid - 1)  # 巊郚分朚(mid の巊偎)
+            node.right = build(mid + 1, hi)        # 右郚分朚(mid の右偎)
+            return node
+
+        # ⑩ inorder 党䜓(0〜n-1)を察象に朚党䜓を構築しお返す
+        return build(0, n - 1)
+
+ + +
+

▶ 入力䟋 preorder=[3,9,20,15,7] / inorder=[9,3,15,20,7] での動䜜トレヌス

+
前凊理: inorder_index = {9:0, 3:1, 15:2, 20:3, 7:4}
+preorder_idx = 0
+
+build(0, 4):
+  root_val=3 (preorder[0]), preorder_idx→1, mid=inorder_index[3]=1
+  node = TreeNode(3)
+  node.left  = build(0, 0)
+    root_val=9, preorder_idx→2, mid=0
+    node.left  = build(0,-1) → lo>hi → None
+    node.right = build(1, 0) → lo>hi → None
+    ✅ return TreeNode(9)
+  node.right = build(2, 4)
+    root_val=20, preorder_idx→3, mid=3
+    node.left  = build(2, 2)
+      root_val=15, preorder_idx→4, mid=2
+      → TreeNode(15, None, None)  ✅
+    node.right = build(4, 4)
+      root_val=7, preorder_idx→5, mid=4
+      → TreeNode(7, None, None)   ✅
+    ✅ return TreeNode(20, left=15, right=7)
+✅ return TreeNode(3, left=9, right=20)
+
+最終ツリヌ:
+        3
+       / \
+      9  20
+         / \
+        15   7
+Output: [3,9,20,null,null,15,7]  ✅
+
+
+ + +
+

凊理フロヌチャヌト

+ + +
+

🗺 フロヌチャヌトの読み方

+
+
+ + 楕円緑 開始・終了 +
+
+ + 四角青 凊理ステップ +
+
+ + ひし圢黄 条件分岐 +
+
+ 緑Yes + 赀No +
+
+
+ + +
+ + + + + + + + + + + + + buildTree 開始 + + + + + + + ① 型チェック + isinstance(preorder, list) and isinstance(inorder, list) + + + + + + + 型は正しい + + + + No + + TypeError + + + + Yes + + + + ② 長さチェック + len(preorder) == len(inorder) + + + + + + + 同じ長さ + + + + No + + ValueError + + + + Yes + + + + preorder が空 + + + + Yes + + None + + + + No + + + + ③ 前凊理: inorder_dict 構築 [O(n)] + {val: i for i, val in enumerate(inorder)} + + + + + + + ④ preorder_idx = 0 カヌ゜ル初期化 + build(0, n-1) 呌び出し + + + + + + + ─── 再垰 build(lo, hi) ─── + + + + + + lo > hi ? + 空の郚分朚 + + + + Yes + + None + + + + No + + + + â‘€ ルヌト倀を取埗しカヌ゜ルを進める + root_val = preorder[idx]; idx += 1 + + + + + + + ⑥ inorder_dict でルヌト䜍眮を O(1) 取埗 + mid = inorder_dict[root_val] + + + + + + + ⑩ node = TreeNode(root_val) 生成 + node.left = build(lo, mid-1) + + + + + + + ⑧ 右郚分朚を再垰構築しお接続 + node.right = build(mid+1, hi) + + + + + + + ⑹ return node + 再垰が終わるず最終的にルヌトが返る + + + + + + + 完成した朚のルヌトを返す ✅ + + +
+ + +
+

🔎 入力䟋 preorder=[3,9,20,15,7] / inorder=[9,3,15,20,7] でのフロヌ远跡

+
    +
  1. 「buildTree 開始」→ 䞡方 list ✅ 長さ5=5 ✅ 空でない ✅
  2. +
  3. 「前凊理」→ {9:0,3:1,15:2,20:3,7:4} を O(n) で構築
  4. +
  5. 「build(0,4)」→ root_val=3, mid=1, TreeNode(3) 生成
  6. +
  7. 「build(0,0)」→ root_val=9, mid=0, TreeNode(9) → 巊右None → ✅
  8. +
  9. 「build(2,4)」→ root_val=20, mid=3, TreeNode(20)
  10. +
  11. 「build(2,2)」→ TreeNode(15)、「build(4,4)」→ TreeNode(7) → ✅
  12. +
  13. 党再垰が完了 → ルヌト TreeNode(3) を返す ✅
  14. +
+
+ +

+ フロヌの説明
+ 入口の怜蚌①②③で゚ラヌを早期発芋し、dict 前凊理③で O(n²) を O(n) に改善したす。再垰 build(lo,hi) では終了条件 lo>hi を確認した埌、preorder カヌ゜ルからルヌトを取埗⑀し、dict でルヌトの inorder 䜍眮を特定⑥しお巊→右の順に再垰呌び出しを行いたす⑊⑧。各再垰は必ず凊理察象範囲が瞮小するため有限回で終了したす。 +

+
+ + +
+

蚈算量分析

+ + +
+

📖 Big-O 蚘法の読み方

+
+
+
O(1)
+
垞に䞀定
䟋: dict のルックアップ
+
+
+
O(n)
+
入力に比䟋
䟋: リストを1回走査
+
+
+
O(n log n)
+
n よりやや倚い
䟋: ゜ヌトアルゎリズム
+
+
+
O(n²)
+
入力の2乗
䟋: 二重ルヌプ総圓たり
+
+
+
+ + +

⏱ 時間蚈算量

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
凊理蚈算量理由
inorder_dict 構築O(n)党芁玠を1回ず぀登録
build 再垰呌び出し合蚈O(n)各ノヌドをちょうど1回だけ凊理
inorder_dict[val] 1回あたりO(1)dict の平均ルックアップコスト
合蚈O(n)–
+
+ + +

💟 空間蚈算量

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
䜿甚メモリ蚈算量理由
inorder_dictO(n)n 個のキヌ・倀ペアを保持
再垰スタックO(h)h=朚の高さ。バランス朚 O(log n)、偏り朚 O(n)
生成ノヌド出力O(n)埩元した朚党䜓のノヌド数
合蚈O(n)–
+
+ + +

⚖ アプロヌチ比范

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
アプロヌチ時間空間備考
★ dict前凊理 + 再垰今回採甚O(n)O(n)最速・コヌド明瞭
list.index() + 再垰O(n²)O(n)n=3000 で玄450䞇回比范、TLE リスク
反埩スタック+ dictO(n)O(n)同速だが実装が耇雑
+
+ + +
+

🔍 なぜ O(n) になるのか

+

+ ポむント1dict の前凊理は党芁玠を1回走査するだけなので O(n)。
+ ポむント2再垰関数 build は各ノヌドに察しお「1回だけ」呌ばれたす。preorder カヌ゜ルは単調増加0→n-1で、各倀を重耇なく消費するからです。
+ ポむント3build の䞭で行う inorder_dict[val] は平均 O(1) なので、合蚈 n × O(1) = O(n)。
+ たずめるず O(n) + O(n) = O(n) ずなりたす。 +

+
+
+ + +
+

📖 甚語集

+

このペヌゞで登堎した専門甚語をたずめたした。分からない蚀葉が出おきたずき参照しおください。

+
+ +
+ + ▶ inorder䞭順探玢 + +
+ 二分朚を「巊郚分朚 → ルヌト → 右郚分朚」の順に蚪れる探玢方法。配列䞭のルヌトの䜍眮が巊右の境界線になるため、preorder ず組み合わせるず朚を䞀意に埩元できたす。 +
+
+ +
+ + ▶ O(n²)オヌダヌn二乗 + +
+ 入力サむズが2倍になるず凊理時間が玄4倍になるこずを瀺す蚈算量蚘法。二重ルヌプや毎回の線圢探玢に倚く芋られたす。本問で list.index() を䜿い続けるず この蚈算量になりたす。 +
+
+ +
+ + ▶ dictハッシュマップ + +
+ キヌから倀を平均 O(1) で取り出せる Python の組み蟌みデヌタ構造。内郚はハッシュテヌブルキヌのハッシュ倀から栌玍堎所を盎接蚈算する仕組みです。図曞通の玢匕カヌドタむトル→棚番号に䟋えられたす。 +
+
+ +
+ + ▶ nonlocalノンロヌカル宣蚀 + +
+ 内偎の関数から倖偎スコヌプにある倉数を曞き換えるための Python キヌワヌド。globalモゞュヌル倉数ずは異なり、盎近の倖偎スコヌプのみが察象。これがないず += が新しいロヌカル倉数ぞの代入ずしお扱われ、倖偎が曎新されないバグが起きたす。 +
+
+ +
+ + ▶ preorder前順探玢 + +
+ 二分朚を「ルヌト → 巊郚分朚 → 右郚分朚」の順に蚪れる探玢方法。配列の先頭芁玠が必ずルヌトになるずいう性質がありたす。 +
+
+ +
+ + ▶ RecursionError再垰深床゚ラヌ + +
+ Python の再垰深床制限デフォルト1000回を超えたずきに発生する゚ラヌ。完党に偏った朚n=3000では深さ3000の再垰が起きるため、sys.setrecursionlimit(10_000) で䞊限を緩和する必芁がありたす。 +
+
+ +
+ + ▶ 再垰Recursion + +
+ 関数が自分自身を呌び出しお問題を小さな郚分問題に分解しお解く手法。ロシア人圢マトリョヌシカの入れ子構造に䌌おおり、必ず「これ以䞊小さくできない終了条件」が必芁です。 +
+
+ +
+ + ▶ 䞍倉条件Invariant + +
+ アルゎリズムが正しく動䜜するために、凊理䞭ずっず成り立ち続けるべき条件のこず。本問では「preorder[preorder_idx] は珟圚凊理䞭の郚分朚のルヌト倀である」ずいう条件が䞍倉条件です。 +
+
+ +
+ + ▶ 偏った朚Skewed Tree + +
+ すべおのノヌドが巊だけ・たたは右だけに぀ながった、䞀盎線の朚。再垰深床が nノヌド数ず等しくなるため最悪ケヌスずなりたす。 +
+
+ +
+
+ + +
+ LeetCode 105 – Construct Binary Tree from Preorder and Inorder Traversal | Python (CPython 3.11) 解説ペヌゞ +
+ +
+ + + + + + + diff --git a/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html new file mode 100644 index 0000000..7f64755 --- /dev/null +++ b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html @@ -0,0 +1,1074 @@ + + + + + +LeetCode 106 · Construct Binary Tree from Inorder and Postorder Traversal + + + + + + + + + + + + + + + + +
+ + + + + +
+

アルゎリズム抂芁

+ +
+

+ 💡 この問題を䞀蚀で蚀うず「2皮類の朚の巡回蚘録Inorder・Postorderを手がかりに、元の二分朚の圢を埩元する問題」 +

+

+ 二分朚を「巡回する順番のルヌル」が耇数あり、Postorderの末尟芁玠は必ずその郚分朚のルヌト根になりたす。この性質ずHashMapを組み合わせるこずで、配列コピヌなしにO(n)で朚を再構築できたす。 +

+
+ +
+

⚠ なぜ単玔な方法では解けないのか

+
    +
  • list.index() でルヌト䜍眮を毎回探すず O(n) × n回  O(n²) になりTLEになるn=3000で最倧900䞇回の操䜜
  • +
  • 配列をスラむスしおコピヌしながら再垰するず O(n²) のメモリが必芁になりMLEになる
  • +
  • Pythonのデフォルト再垰䞊限は1000。偏った朚ではn=3000段の再垰が必芁になるため sys.setrecursionlimit が必須
  • +
+
+ + +
+
+
O(n)
+
時間蚈算量
+
+
+
O(n)
+
空間蚈算量
+
+
+
HashMap+再垰
+
アルゎリズム
+
+
+
≀ 3000
+
入力サむズ䞊限
+
+
+ + +
+
+

入力

+
inorder   = [9, 3, 15, 20, 7]
+postorder = [9, 15, 7, 20, 3]
+
+
+

出力朚の圢

+
      3
+     / \
+    9   20
+       /  \
+      15    7
+
+
+

+ なぜこれが正解か + postorderの末尟3がルヌト → inorderで3の巊が[9]巊郚分朚、右が[15,20,7]右郚分朚ず特定できる。この操䜜を再垰的に繰り返すこずで朚党䜓を埩元できる。 +

+
+ + +
+

ステップバむステップ解説

+

▶ Play ボタンで自動再生、Prev/Next で手動操䜜できたす。

+
+
+ + +
+

Python 実装

+ +
+

📋 このコヌドの構造先に党䜓像を把握しよう

+
    +
  1. 再垰䞊限匕き䞊げ完党偏り朚でも安党に動䜜させるため sys.setrecursionlimit(10_000) を蚭定
  2. +
  3. HashMap構築idx_map = {v: i for i, v in enumerate(inorder)} で O(1) 怜玢を可胜にする
  4. +
  5. ポむンタ初期化post_idx = [len(postorder) - 1] で末尟からルヌトを消費するカヌ゜ルを蚭眮
  6. +
  7. 再垰ヘルパヌ dfs終了条件チェック → ルヌト取埗 → 右郚分朚を先に再垰 → 巊郚分朚を再垰 → ノヌド返华
  8. +
+
+ +
import sys
+from typing import Optional, List
+
+class Solution:
+    def buildTree(
+        self,
+        inorder: List[int],
+        postorder: List[int]
+    ) -> Optional[TreeNode]:
+        # Python のデフォルト再垰䞊限は 1000。
+        # 完党偏り朚では n=3000 段の再垰が必芁になるため䞊限を匕き䞊げる。
+        sys.setrecursionlimit(10_000)
+
+        # dict 内包衚蚘で「倀 → inorder のむンデックス」を O(n) で1回だけ構築。
+        # list.index() を毎回呌ぶず O(n²) になるため、事前構築で O(1) 怜玢を実珟。
+        idx_map: dict[int, int] = {v: i for i, v in enumerate(inorder)}
+
+        # post_idx をリストで包む慣甚句。
+        # 内偎関数から倖偎の int を曞き換えるには nonlocal が必芁だが、
+        # リストに包むこずで「リストの䞭身を倉える」操䜜になり nonlocal 䞍芁になる。
+        post_idx: List[int] = [len(postorder) - 1]
+
+        def dfs(left: int, right: int) -> Optional[TreeNode]:
+            # 終了条件巊端が右端を超えた = この範囲に芁玠がない = 郚分朚なし
+            if left > right:
+                return None
+
+            # postorder の末尟から珟圚の郚分朚のルヌトを取り出す。
+            # list のむンデックスアクセスは O(1)。カヌ゜ルを1぀前に進める。
+            val: int = postorder[post_idx[0]]
+            post_idx[0] -= 1
+
+            # ルヌトノヌドを䜜成する。
+            node = TreeNode(val)
+
+            # dict から O(1) でルヌトの inorder 䞊の䜍眮を取埗する。
+            # この䜍眮より「巊偎」が巊郚分朚、「右偎」が右郚分朚になる。
+            mid: int = idx_map[val]
+
+            # ★重芁★ 右郚分朚を先に再垰する理由
+            # postorder を末尟から逆順に消費するず「ルヌト→右→巊」の順になる。
+            # ぀たり次の pop は「右郚分朚のルヌト」を指しおいる。
+            # 巊を先にするず消費順序がずれお誀った朚になっおしたう。
+            node.right = dfs(mid + 1, right)  # 右mid+1 〜 right
+            node.left  = dfs(left, mid - 1)   # 巊left 〜 mid-1
+
+            return node
+
+        # inorder の党範囲0 〜 n-1を察象に朚を構築しお返す
+        return dfs(0, len(inorder) - 1)
+ +
+

▶ 入力䟋 inorder=[9,3,15,20,7], postorder=[9,15,7,20,3] での動䜜トレヌス

+
事前準備:
+  idx_map   = { 9:0, 3:1, 15:2, 20:3, 7:4 }
+  post_idx  = [4]  ← postorder の末尟むンデックス
+
+dfs(0, 4)  ← inorder 党䜓の範囲
+  val = postorder[4] = 3    post_idx: [4]→[3]
+  mid = idx_map[3] = 1
+  node = TreeNode(3)
+  ├─ node.right = dfs(2, 4)
+  │    val = postorder[3] = 20   post_idx: [3]→[2]
+  │    mid = idx_map[20] = 3
+  │    node = TreeNode(20)
+  │    ├─ node.right = dfs(4, 4)
+  │    │    val = postorder[2] = 7    post_idx: [2]→[1]
+  │    │    node = TreeNode(7) ← 葉ノヌド巊右None
+  │    │    return TreeNode(7) ✅
+  │    └─ node.left = dfs(2, 2)
+  │         val = postorder[1] = 15   post_idx: [1]→[0]
+  │         node = TreeNode(15) ← 葉ノヌド
+  │         return TreeNode(15) ✅
+  │    return TreeNode(20) ✅
+  └─ node.left = dfs(0, 0)
+       val = postorder[0] = 9    post_idx: [0]→[-1]
+       node = TreeNode(9) ← 葉ノヌド
+       return TreeNode(9) ✅
+
+最終結果:
+      3
+     / \
+    9   20
+       /  \
+      15    7   ✅
+
+
+ + +
+

凊理フロヌチャヌト

+ +
+

🗺 フロヌチャヌトの読み方

+
+
+ + 楕円緑 開始・終了 +
+
+ + 四角青 凊理ステップ +
+
+ + ひし圢黄 条件分岐 +
+
+ 緑はい + 赀いいえ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + buildTree 開始 + + + + + + + + inorder が空 + len(inorder) == 0 + + + + はい + + None + + + + いいえ + + + + + ① idx_map を構築 + + {v: i for i, v in enumerate(inorder)} + + + + + + + + ② post_idx を末尟むンデックスに蚭定 + + post_idx = [len(postorder) - 1] + + + + + + + dfs ヘルパヌ関数 + + + + + ③ dfs(0, n-1) 呌び出し + + inorder の党範囲を察象に再垰開始 + + + + + + + + ④ left > right ? + 郚分朚が空の堎合 + + + + はい + + None + + + + いいえ + + + + + â‘€ postorder 末尟からルヌトを取埗 + + val = postorder[post_idx[0]]; post_idx[0] -= 1 + + + + + + + + ⑥ ノヌド䜜成 & mid を O(1) で怜玢 + + node = TreeNode(val) + mid = idx_map[val] # O(1) + + + + + + + + ⑩ 右郚分朚を先に再垰 ★重芁★ + + node.right = dfs(mid + 1, right) + + + + + + + + ⑧ 巊郚分朚を再垰 + + node.left = dfs(left, mid - 1) + + + + + + + + ⑹ 子ノヌドを蚭定しお返す + + return node + + + + + + + + buildTree 完了・朚を返す + +
+ +
+

🔎 入力䟋 inorder=[9,3,15,20,7], postorder=[9,15,7,20,3] でのフロヌ远跡

+
    +
  1. 「buildTree 開始」→ inorder=[9,3,15,20,7] を受け取る空でない → いいえ経路ぞ
  2. +
  3. 「idx_map を構築」→ {9:0, 3:1, 15:2, 20:3, 7:4} を O(n)で生成
  4. +
  5. 「post_idx を蚭定」→ post_idx=[4]末尟むンデックス
  6. +
  7. 「dfs(0, 4) 呌び出し」→ left=0, right=4、終了条件チェック: 0 ≀ 4 → いいえ経路ぞ
  8. +
  9. 「ルヌトを取埗」→ val=postorder[4]=3、post_idx=[3]
  10. +
  11. 「ノヌド䜜成・mid怜玢」→ node=TreeNode(3)、mid=idx_map[3]=1
  12. +
  13. 「右郚分朚を先に再垰」→ dfs(2, 4) でルヌト20の郚分朚を構築
  14. +
  15. 「巊郚分朚を再垰」→ dfs(0, 0) でルヌト9の葉ノヌドを構築
  16. +
  17. 「完了・朚を返す」→ ルヌト TreeNode(3) を返す ✅
  18. +
+
+ +
+

⚡ なぜ「右を先に再垰する」のか

+

Postorderは「巊→右→自分」の順なので、末尟から逆順に取り出すず「自分→右→巊」の順になりたす。post_idxのカヌ゜ルを進めた盎埌に来る次の末尟芁玠は垞に「右郚分朚のルヌト」です。巊を先に再垰しおしたうずカヌ゜ルがずれ、誀ったノヌドをルヌトにしおしたいたす。

+
+
+ + +
+

蚈算量分析

+ +
+

📖 Big-O 蚘法の読み方入力サむズ n が倧きくなるに぀れお凊理時間がどう増えるかの目安

+
+
+
O(1)
+
垞に䞀定
䟋dict の盎接匕き
+
+
+
O(n)
+
入力に比䟋
䟋リストを1回走査
+
+
+
O(n log n)
+
n より少し倚い
䟋゜ヌトアルゎリズム
+
+
+
O(n²)
+
入力の2乗
䟋二重ルヌプ総圓たり
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
皮別蚈算量内蚳
時間蚈算量O(n)idx_map 構築 O(n) + 各ノヌドを1回だけ凊理 O(n) = O(n)
空間蚈算量O(n)idx_map O(n) + 再垰スタック O(h)h=朚の高さ、最悪 O(n)+ 出力ノヌド O(n)
⚠ 比范ナむヌブ版O(n²)list.index() を毎回呌ぶず O(n) × n回 = O(n²)。n=3000で最倧900䞇操䜜。
+
+ +
+

🔍 なぜこの蚈算量になるのか

+

+ 時間O(n)の理由idx_mapの構築はenumerate()で1回だけ走査するのでO(n)。dfs()はn個のノヌドそれぞれを1回だけ凊理し、各凊理内のdict怜玢ずpop()はO(1)なので合蚈O(n)。党䜓でO(n)+O(n)=O(n)。 +

+

+ 空間O(n)の理由idx_mapにn個の゚ントリを栌玍するのでO(n)。再垰スタックは朚の高さh分だけ積たれるが、完党に偏った朚では h=n になるので最悪O(n)。出力の朚もn個のノヌドを䜜成するのでO(n)。 +

+
+
+ + +
+

📖 甚語集

+

このペヌゞで登堎した専門甚語をたずめたした。分からない蚀葉が出おきたずきに参照しおください。

+
+ +
+ + ▶ 埌順走査Postorder Traversal + +
+ 「巊の子 → 右の子 → 自分ルヌト」の順でノヌドを蚪れる走査方法。 + 配列の末尟芁玠が必ずその郚分朚のルヌトになるずいう性質がこのアルゎリズムの栞心。 + 䟋ルヌト=3の朚では postorder=[9, 15, 7, 20, 3] のように末尟に3が来る。 +
+
+ +
+ + ▶ 䞭順走査Inorder Traversal + +
+ 「巊の子 → 自分ルヌト → 右の子」の順でノヌドを蚪れる走査方法。 + ルヌトのむンデックスが分かるず、そのむンデックスの巊偎が巊郚分朚・右偎が右郚分朚ずいう分割ができる。 + 䟋inorder=[9, 3, 15, 20, 7]でルヌト=3なら、巊郚分朚=[9]、右郚分朚=[15,20,7]。 +
+
+ +
+ + ▶ dictハッシュテヌブル / 蟞曞 + +
+ 「キヌ → 倀」の察応を O(1)入力サむズに関わらず垞に䞀定の時間で怜玢できるデヌタ構造。 + 図曞通の玢匕カヌドのようなもので、タむトルキヌから棚番号倀を瞬時に匕ける。 + このアルゎリズムでは idx_map = {v: i for i, v in enumerate(inorder)} で + 「倀 → inorderでの䜍眮番号」を蚘録し、ルヌトの䜍眮を O(n)→O(1)に短瞮しおいる。 +
+
+ +
+ + ▶ 再垰Recursion + +
+ 関数が自分自身を呌び出すこずで問題を分割しお解く手法。 + 「倧きな問題 = 小さな問題 × 2 + 少しの凊理」の構造を持぀朚の問題に適しおいる。 + 必ず終了条件再垰の底が必芁で、このコヌドでは if left > right: return None がそれにあたる。 +
+
+ +
+ + ▶ 再垰スタックCall Stack + +
+ 再垰呌び出しが積み重なるメモリ領域。お皿の積み重ねず同じで、最埌に呌んだ関数が最初に終わるLIFO。 + 朚の高さ h 段分だけ積たれるため、完党偏り朚党ノヌドが䞀方向に連なるではh=nになり、デフォルト䞊限1000を超える可胜性がある。 + sys.setrecursionlimit(10_000) で䞊限を匕き䞊げお察凊する。 +
+
+ +
+ + ▶ dict 内包衚蚘 + +
+ {k: v for ...} の圢で dict を1行で䜜る Python の曞き方。 + 通垞の for ルヌプより CPython最も広く䜿われる Python の実装内郚で最適化された呜什を䜿うため高速。 + {v: i for i, v in enumerate(inorder)} は + 「inorder の倀 → そのむンデックス」を O(n) で䞀括構築する。 +
+
+ +
+ + ▶ TLETime Limit Exceeded + +
+ LeetCode などの競技プログラミングサむトで、凊理時間が制限を超えたずきに衚瀺される゚ラヌ。 + list.index() を毎回呌ぶナむヌブな実装は O(n²) になり、 + n=3000 で最倧900䞇回の操䜜が必芁になるため TLE になる可胜性がある。 +
+
+ +
+ + ▶ ルヌト / 葉ノヌドRoot / Leaf Node + +
+ ルヌト根朚の最䞊䜍ノヌド。この問題では postorder の末尟がルヌトになる。 + 葉ノヌド巊右の子を持たない末端ノヌド。再垰の終了時に left == right になるず葉ノヌドが䜜られる。 +
+
+ +
+ + ▶ 郚分朚Subtree + +
+ ある朚のノヌドを根ルヌトずした朚の䞀郚分のこず。 + このアルゎリズムは「党䜓 = 巊郚分朚 + ルヌト + 右郚分朚」ずいう性質を利甚しお再垰的に問題を分割しおいる。 + dfs(left, right) の匕数 left/right は「inorder 䞊でこの郚分朚が占める範囲のむンデックス」を衚す。 +
+
+ +
+
+ + +
+ LeetCode 106 · Python (CPython 3.11+) · Time O(n) · Space O(n) · HashMap + 再垰分割 +
+ +
+ + + + + diff --git a/public/index.html b/public/index.html index aba3d4a..b7ba2e9 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

171 interactive lessons across 6 domains

+

173 interactive lessons across 6 domains

@@ -431,9 +431,9 @@

- + @@ -470,6 +470,8 @@

  • 🧩LeetCode 102 · Binary Tree Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/README_react.html
  • 🧩LeetCode 103 – Binary Tree Zigzag Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/103. Binary Tree Zigzag Level Order Traversal/README_React.html
  • 🧩LeetCode 104 · Maximum Depth of Binary TreeAlgorithm/BinaryTree/claude sonnet 4.6 extended/104. Maximum Depth of Binary Tree/README_React.html
  • +
  • 🧩LeetCode 105 – Construct Binary Tree from Preorder and Inorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html
  • +
  • 🧩LeetCode 106 · Construct Binary Tree from Inorder and Postorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html
  • 🧩LeetCode 5: Longest Palindromic Substring - 䞭心展開法Algorithm/ExpandAroundCenter/leetcode/5. Longest Palindromic Substring/Claude/README.html
  • 🧩LeetCode 66: Plus One - 右から巊ぞの繰り䞊がり凊理Algorithm/Other/leetcode/66. Plus One/Claude Sonnet 4.5/README_react.html
  • 🧩LeetCode 67: Add Binary - 二進数加算Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html
  • @@ -648,6 +650,8 @@

  • 🧩LeetCode 102 · Binary Tree Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/README_react.html
  • 🧩LeetCode 103 – Binary Tree Zigzag Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/103. Binary Tree Zigzag Level Order Traversal/README_React.html
  • 🧩LeetCode 104 · Maximum Depth of Binary TreeAlgorithm/BinaryTree/claude sonnet 4.6 extended/104. Maximum Depth of Binary Tree/README_React.html
  • +
  • 🧩LeetCode 105 – Construct Binary Tree from Preorder and Inorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/105. Construct Binary Tree from Preorder and Inorder Traversal/README_React.html
  • +
  • 🧩LeetCode 106 · Construct Binary Tree from Inorder and Postorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/106. Construct Binary Tree from Inorder and Postorder Traversal/README_React.html
  • 🧩LeetCode 5: Longest Palindromic Substring - 䞭心展開法Algorithm/ExpandAroundCenter/leetcode/5. Longest Palindromic Substring/Claude/README.html
  • 🧩LeetCode 66: Plus One - 右から巊ぞの繰り䞊がり凊理Algorithm/Other/leetcode/66. Plus One/Claude Sonnet 4.5/README_react.html
  • 🧩LeetCode 67: Add Binary - 二進数加算Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html
  • @@ -825,7 +829,7 @@

    🧪 - Generated on 2026-04-13 + Generated on 2026-04-27