From 909b3d30de70839036becd3f57274ad5cf47d287 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Wed, 18 Mar 2026 20:50:42 +0900 Subject: [PATCH 1/7] Add LeetCode 94. Binary Tree Inorder Traversal (Claude Sonnet 4.6 Extended) implementation in Python, Rust, and TypeScript --- .../Binary_Tree_Inorder_Traversal_Rust.md | 163 ++ .../Binary_Tree_Inorder_Traversal_python.md | 183 ++ ...inary_Tree_Inorder_Traversal_typescript.md | 153 ++ .../README.md | 362 ++++ .../README_react.html | 1570 +++++++++++++++++ .../README_react.html | 1570 +++++++++++++++++ public/index.html | 10 +- 7 files changed, 4007 insertions(+), 4 deletions(-) create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_Rust.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_python.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_typescript.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html create mode 100644 public/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_Rust.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_Rust.md new file mode 100644 index 0000000..16a77e1 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_Rust.md @@ -0,0 +1,163 @@ +# Binary Tree Inorder Traversal — Rust Edition + +## 1. 問題の分析 + +### 競技プログラミング視点での分析 + +- 全ノードを一度だけ訪れる **O(N) 時間・O(N) 空間**が理論下界 +- LeetCode の Rust 環境では `TreeNode` が `Option>>` でラップされており、**所有権の移動なしに借用で読み取る** 設計が必須 +- 明示スタックによる反復実装でコールスタック消費を排除 + +### 業務開発視点での分析 + +- `Option>>` という複合型を安全に扱うため、`.borrow()` による共有参照と `Option` の `?`/`if let` による null 安全な展開が鍵 +- `Vec` への追記は `push` のみで副作用をローカルに限定 → Pure function に近い設計 +- `Result` は不要(入力が空 = 空ベクタを返すだけで、エラー状態がない) + +### Rust特有の考慮点 + +- `Rc>` の共有所有権モデル:`clone()` はポインタのコピーのみ(O(1)) +- `.borrow()` で `Ref` を取得 → 借用スコープを最小化してデッドロック回避 +- スタックの型を `Rc>` とすることで、非 null 要素のみを格納できる + +--- + +## 2. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | Rust実装コスト | 安全性 | 可読性 | 備考 | +| --------------------------- | ---------- | ---------- | -------------- | ------ | ------ | --------------------------------------------------- | +| **A: 再帰 DFS** | O(N) | O(N) | 低 | 高 | 最高 | コールスタック深さN、深い木でスタックオーバーフロー | +| **B: 反復(明示スタック)** | O(N) | O(N) | 中 | 高 | 高 | Follow-up要件を満たす。`Rc::clone`でO(1)コピー | +| **C: Morris Traversal** | O(N) | O(1) | 非常に高 | 低 | 低 | `RefCell`の可変借用が複数箇所で必要、実装困難 | + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択したアプローチ**: **B: 反復(明示スタック)** +- **理由**: + - Follow-upの反復解要件を満たす + - `Rc>` モデルで Morris は `borrow_mut()` の多重借用を誘発しやすく危険 + - `Rc::clone()` はポインタカウントのインクリメントのみで、ノード値コピーなし + - スタック `Vec>>` で型安全かつ非 null 要素のみ管理 + +- **Rust特有の最適化ポイント**: + - `Rc::clone(&node)` の明示的クローンでコスト意識を表現 + - `.borrow()` の借用スコープを `{}` ブロックで最小化(借用の早期解放) + - `Vec::with_capacity` でリアロケーション回数を削減(N≤100 なので省略可) + +--- + +## 4. 実装コード + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 2.19MB +// Beats 74.02% + +// leetcode環境では use std::rc::Rc; use std::cell::RefCell; が提供済み + +// ---- メイン実装 ---- + +/// Binary Tree Inorder Traversal(反復・明示スタック実装) +/// +/// アルゴリズム: +/// 1. current カーソルを根から開始 +/// 2. current が Some の間、左端までスタックに積む(Rc::clone でポインタコピー) +/// 3. スタックから pop → 値を記録 → current を右子に移す +/// 4. current と stack が両方空になったら終了 +/// +/// # Arguments +/// * `root` - 二分木の根ノード(`None` = 空木) +/// +/// # Returns +/// 中順走査の値ベクタ(空木の場合は空ベクタ) +/// +/// # Complexity +/// - Time: O(N) — 全ノードを一度だけ訪問 +/// - Space: O(N) — 明示スタック最大深さ N(最悪: 左に偏った木) +pub fn inorder_traversal(root: Option>>) -> Vec { + // 結果バッファ(N ≤ 100 なのでデフォルト容量で十分) + let mut result: Vec = Vec::new(); + + // 明示スタック:非 null ノードのみ格納(型レベルで None 混入を排除) + let mut stack: Vec>> = Vec::new(); + + // カーソル:Option で「未訪問ノードあり」「なし」を型安全に表現 + let mut current: Option>> = root; + + // current(未訪問)とstack(保留)のいずれかが残る間ループ + while current.is_some() || !stack.is_empty() { + + // ---- フェーズ1: 左端まで潜りながらスタックに積む ---- + while let Some(node_rc) = current { + // Rc::clone はポインタカウントのインクリメントのみ(O(1)、コピーコストなし) + stack.push(Rc::clone(&node_rc)); + + // .borrow() スコープを最小化: 左子のクローンを取得したら即解放 + let left = node_rc.borrow().left.clone(); + current = left; // 左へ進む(None なら次のwhileを脱出) + } + + // ---- フェーズ2: スタック top を取り出して訪問 ---- + // stack.is_empty() でないことはループ条件で保証済み → unwrap 安全 + if let Some(node_rc) = stack.pop() { + // 借用スコープを {} で明示的に限定(右子取得前に解放) + let (val, right) = { + let node = node_rc.borrow(); // Ref + (node.val, node.right.clone()) + }; // ← ここで Ref が drop され、借用解放 + + // 中順で値を記録 + result.push(val); + + // ---- フェーズ3: 右部分木へカーソルを移す ---- + current = right; // None なら次ループで即 pop フェーズへ + } + } + + result +} +``` + +--- + +## 5. アルゴリズム動作トレース + +`root = [1, null, 2, 3]` を例に各フェーズを可視化します。 + +``` +ツリー構造: + 1 + \ + 2 + / + 3 +``` + +| ステップ | current | stack(底→top) | result | 操作 | +| -------- | ------------ | --------------- | ------- | --------------------------------- | +| 初期 | Some(1) | [] | [] | — | +| Ph1 | None | [1] | [] | 1 を push、left=None で停止 | +| Ph2 | — | [] | [1] | pop→1、val=1 を記録 | +| Ph3 | Some(2) | [] | [1] | current = right(2) | +| Ph1 | Some(3)→None | [2,3] | [1] | 2 push → 3 push、left=None で停止 | +| Ph2 | — | [2] | [1,3] | pop→3、val=3 を記録 | +| Ph3 | None | [2] | [1,3] | current = right(None) | +| Ph2 | — | [] | [1,3,2] | pop→2、val=2 を記録 | +| Ph3 | None | [] | [1,3,2] | ループ終了 | + +**Output: `[1, 3, 2]` ✅** + +--- + +## Rust固有の最適化観点まとめ + +| 観点 | 本実装での適用 | +| ----------------- | ---------------------------------------------------------------------- | +| **所有権管理** | `Rc::clone` でポインタ共有(ノード値コピーなし) | +| **借用の最小化** | `{ let node = node_rc.borrow(); ... }` で借用スコープを即解放 | +| **null 安全性** | `Option>>` + `while let` で None を型レベルで排除 | +| **Pure function** | 入力ツリーへの書き込みゼロ(`.borrow()` のみ、`.borrow_mut()` 不使用) | +| **パニック制御** | `unwrap()` を排除し `if let` / `while let` で安全展開 | diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_python.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_python.md new file mode 100644 index 0000000..0b63c62 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_python.md @@ -0,0 +1,183 @@ +# Binary Tree Inorder Traversal — Python Edition + +## 1. 問題分析結果 + +### 競技プログラミング視点 + +- **制約分析**: N ≤ 100 と極小。O(N) 時間・O(N) 空間が理論下界 +- **最速手法**: 明示スタックによる反復実装。Python の関数呼び出しオーバーヘッドを回避 +- **CPython最適化**: `list.append()` は C 実装で O(1) 均償。`list.pop()` も同様 + +### 業務開発視点 + +- **型安全設計**: `Optional[TreeNode]`、`list[int]` で Pylance 完全対応 +- **エラーハンドリング**: 空木(`None`)はガード節で即 `[]` を返し、例外を発生させない +- **可読性**: フェーズをコメントで明示し、意図を self-documenting に + +### Python特有分析 + +- **データ構造選択**: スタックは `list`(`append`/`pop` ともに O(1) で deque 不要) +- **再帰 vs 反復**: Python のデフォルト再帰上限は 1,000。反復実装が本番安全 +- **`while` + `list.pop()`**: CPython の C レイヤーで動作し最速 + +--- + +## 2. 採用アルゴリズムと根拠 + +- **選択**: 反復(明示スタック)+ カーソルポインタ方式 +- **Python最適化戦略**: `list.append` / `list.pop` を直接呼び出し、属性ルックアップを最小化 +- **トレードオフ**: 再帰より数行多いが、スタックオーバーフロー耐性と Follow-up 要件を同時に満たす + +--- + +## 3. 実装パターン + +### 業務開発版(型安全・可読性重視) + +```python +# Runtime 0 ms +# Beats 100.00% +# Memory 19.22 MB +# Beats 85.80% + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right + +from __future__ import annotations +from typing import Optional + + +class Solution: + """ + Binary Tree Inorder Traversal + 中順走査(左 → 根 → 右)を反復スタックで実装。 + Follow-up: 再帰を使わない反復解。 + """ + + def inorderTraversal(self, root: Optional[TreeNode]) -> list[int]: + """ + 二分木の中順走査を反復で実行する。 + + Args: + root: 二分木の根ノード(None = 空木) + + Returns: + 中順走査の値リスト。空木の場合は空リスト。 + + Complexity: + Time: O(N) — 全ノードを一度だけ訪問 + Space: O(N) — 明示スタックの最大深さ(最悪: 左偏木) + """ + # ガード: 空木は即座に空リストを返す + if root is None: + return [] + + result: list[int] = [] + stack: list[TreeNode] = [] + current: Optional[TreeNode] = root + + while current is not None or stack: + + # ── フェーズ1: 左端まで潜りながらスタックに積む ────────────── + while current is not None: + stack.append(current) # 右・自身は後回し + current = current.left # 左へ進む + + # ── フェーズ2: スタック top を取り出して訪問 ───────────────── + # ループ条件より stack が空でないことは保証済み + node: TreeNode = stack.pop() + result.append(node.val) # ← 中順で値を記録 + + # ── フェーズ3: 右部分木へカーソルを移す ────────────────────── + current = node.right # None なら次ループで即 pop フェーズへ + + return result +``` + +--- + +### 競技プログラミング版(性能最優先) + +```python +from typing import Optional + + +class Solution: + def inorderTraversal(self, root: Optional[TreeNode]) -> list[int]: + """ + 中順走査 反復実装(最速版) + + Time: O(N) + Space: O(N) + """ + res: list[int] = [] + stk: list[TreeNode] = [] + cur = root + + while cur or stk: + # 左端まで積む + while cur: + stk.append(cur) + cur = cur.left + # 訪問 → 右へ + cur = stk.pop() + res.append(cur.val) + cur = cur.right + + return res +``` + +--- + +## 4. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | +| --------------------------- | ---------- | ---------- | ---------------- | ------ | ------------------ | ------------- | --------------------------------------------------- | +| **A: 再帰 DFS** | O(N) | O(N) | 低 | ★★★ | — | 不適 | 再帰上限 1,000 でスタックオーバーフローリスク | +| **B: 反復(明示スタック)** | O(N) | O(N) | 中 | ★★★ | `list` | 適 | Follow-up 要件を満たす。本実装 ✅ | +| **C: Morris Traversal** | O(N) | O(1) | 高 | ★☆☆ | — | 不適 | ノード書き換えで副作用あり、Python では実装コスト高 | + +--- + +## 5. 動作トレース + +`root = [1, null, 2, 3]` を例に。 + +``` +ツリー: + 1 + \ + 2 + / + 3 +``` + +| ステップ | current | stack(底→top) | result | 操作 | +| -------- | ------- | --------------- | --------- | --------------------------- | +| 初期 | 1 | [] | [] | — | +| Ph1 | None | [1] | [] | 1 を push、left=None で停止 | +| Ph2 | — | [] | [1] | pop→1、val=1 を記録 | +| Ph3 | 2 | [] | [1] | current = right(2) | +| Ph1 | 3→None | [2, 3] | [1] | 2 push → 3 push | +| Ph2 | — | [2] | [1, 3] | pop→3、val=3 を記録 | +| Ph3 | None | [2] | [1, 3] | current = right(None) | +| Ph2 | — | [] | [1, 3, 2] | pop→2、val=2 を記録 | +| Ph3 | None | [] | [1, 3, 2] | ループ終了 | + +**Output: `[1, 3, 2]` ✅** + +--- + +## Python固有の最適化観点まとめ + +| 観点 | 本実装での適用 | +| -------------------- | -------------------------------------------------------------- | +| **Pylance 型安全** | `Optional[TreeNode]`・`list[int]`・`list[TreeNode]` で完全対応 | +| **CPython 最速操作** | `list.append` / `list.pop` は C 実装 O(1) 均償 | +| **再帰回避** | 明示スタックでデフォルト再帰上限(1,000)の制約を完全回避 | +| **Pure function** | 入力ツリーへの書き込みゼロ、副作用なし | +| **エッジケース** | `root is None` ガードで空木を即返却、例外発生なし | diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_typescript.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_typescript.md new file mode 100644 index 0000000..7fd3249 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/Binary_Tree_Inorder_Traversal_typescript.md @@ -0,0 +1,153 @@ +# Binary Tree Inorder Traversal + +## 1. 問題の分析 + +### 競技プログラミング視点での分析 + +- **中順走査(左→根→右)** の順でノード値を収集する古典的な木探索問題 +- ノード数 N ≤ 100 と制約が小さいため、漸近的複全体ノードを一度訪れる O(N) が理論下界 +- Follow-up:**反復(スタック)実装**が求められており、再帰によるコールスタック消費を回避できる + +### 業務開発視点での分析 + +- `TreeNode | null` という Union 型を正確に扱う null 安全性が重要 +- 再帰は可読性が高いが、深い木でスタックオーバーフローリスクあり +- イテレーティブ実装は明示的スタック管理により**実行時安全性**が高い + +### TypeScript特有の考慮点 + +- `TreeNode | null` の型ガードで null チェックを厳密に +- `readonly` 修飾子と `as const` でイミュータブルな中間状態を表現 +- スタックの型を `TreeNode[]` と明示し、型推論を最大活用 + +--- + +## 2. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +| --------------------------- | ---------- | ---------- | ------------ | -------- | ------ | ---------------------------- | +| **A: 再帰 DFS** | O(N) | O(N)※ | 低 | 高 | 最高 | ※コールスタック深さ N | +| **B: 反復(明示スタック)** | O(N) | O(N) | 中 | 高 | 高 | スタックオーバーフロー回避 | +| **C: Morris Traversal** | O(N) | O(1) | 高 | 中 | 低 | ポインタ書き換えで副作用あり | + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択したアプローチ**: **B: 反復(明示スタック)** +- **理由**: + - Follow-upで反復解が明示的に要求されている + - コールスタックを消費しないため、深い木でも安全 + - `TreeNode[]` スタックで型安全に実装可能 + - Morris は入力ツリーを一時変更する副作用があり Pure function の原則に反する + +- **TypeScript特有の最適化ポイント**: + - `current: TreeNode | null` によるカーソル変数の明確な型定義 + - `stack: TreeNode[]` で非 null 要素のみ格納し、pop 後のアサーション不要化 + - `result: number[]` の型推論によりキャスト不要 + +--- + +## 4. 実装コード + +```typescript +// Runtime 0 ms +// Beats 100.00% +// Memory 55.30 MB +// Beats 63.69% + +/** + * 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) + * } + * } + */ + +/** + * Binary Tree Inorder Traversal(反復・明示スタック実装) + * + * アルゴリズム: + * 1. current ポインタを根から開始 + * 2. current が非 null の間、左端までスタックに積む + * 3. スタックから pop → 値を記録 → current を右子に移す + * 4. current と stack が両方空になったら終了 + * + * @param root - 二分木の根ノード(null = 空木) + * @returns 中順走査の値配列 + * @complexity Time: O(N), Space: O(N) N = ノード数 + */ +function inorderTraversal(root: TreeNode | null): number[] { + // 入力ガード:空木は即座に空配列を返す(型安全) + if (root === null) return []; + + const result: number[] = []; // 走査結果を蓄積 + const stack: TreeNode[] = []; // 明示スタック(非 null のみ格納) + let current: TreeNode | null = root; + + // current(未訪問ノード)とstack(保留ノード)のいずれかが残る間ループ + while (current !== null || stack.length > 0) { + // フェーズ1: 左端まで潜りながらスタックに積む + while (current !== null) { + stack.push(current); // 右・自身は後回し + current = current.left; // 左へ進む + } + + // フェーズ2: スタック top を取り出して訪問 + // stack.length > 0 保証済みなので non-null assertion は安全 + const node = stack.pop()!; // TreeNode 確定 + result.push(node.val); // ← 中順で値を記録 + + // フェーズ3: 右部分木へカーソルを移す(null なら次ループでpopへ) + current = node.right; + } + + return result; +} +``` + +--- + +## 5. アルゴリズム動作トレース + +`root = [1, null, 2, 3]` を例に各フェーズを可視化します。 + +``` +ツリー構造: + 1 + \ + 2 + / + 3 +``` + +| ステップ | current | stack(底→top) | result | 操作 | +| -------- | ------- | --------------- | ------- | ------------------------------- | +| 初期 | 1 | [] | [] | — | +| Ph1 | null | [1] | [] | 1 を push、左=null で停止 | +| Ph2 | — | [] | [1] | pop→1、val=1 を記録 | +| Ph3 | 2 | [] | [1] | current = 右(2) | +| Ph1 | 3 | [2,3] | [1] | 2 push → 3 push、左=null で停止 | +| Ph2 | — | [2] | [1,3] | pop→3、val=3 を記録 | +| Ph3 | null | [2] | [1,3] | current = 右(null) | +| Ph2 | — | [] | [1,3,2] | pop→2、val=2 を記録 | +| Ph3 | null | [] | [1,3,2] | ループ終了 | + +**Output: `[1, 3, 2]` ✅** + +--- + +## TypeScript固有の最適化観点まとめ + +| 観点 | 本実装での適用 | +| ---------------------- | -------------------------------------------------------------------------- | +| **null 安全性** | `TreeNode \| null` Union 型 + `!` アサーション(スタック長保証後のみ使用) | +| **型推論** | `result`, `stack` の型はイニシャライザから自動推論 | +| **イミュータブル操作** | `result.push` のみで元ツリー構造を一切変更しない Pure function | +| **コンパイル時安全性** | `stack: TreeNode[]` により pop 結果が `TreeNode \| undefined` と明確化 | diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README.md new file mode 100644 index 0000000..33882a3 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README.md @@ -0,0 +1,362 @@ +# Binary Tree Inorder Traversal - 反復スタックで中順走査を完全攻略 + +

目次

+ +- [概要](#overview) +- [アルゴリズム要点 TL;DR](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +**LeetCode 94 — Binary Tree Inorder Traversal** + +二分木の根ノード `root` が与えられるとき、**中順走査(左 → 根 → 右)** の順でノードの値を収集し、リストとして返す。 + +| 項目 | 内容 | +| -------------- | ----------------------------------------------------------------------- | +| 入力 | `root: Optional[TreeNode]`(ノード数 0 ≤ N ≤ 100、値 −100 ≤ val ≤ 100) | +| 出力 | `list[int]`(中順走査の値列) | +| 想定データ構造 | `TreeNode`(`val / left / right`) | +| Follow-up | **再帰を使わない反復解**を実装せよ | + +### 要件まとめ + +- 全ノードをちょうど一度訪問し、中順(左→根→右)で値を記録する +- 空木(`root is None`)は空リスト `[]` を返す +- Python のデフォルト再帰上限(1,000)に依存しない反復実装を提供する + +--- + +

アルゴリズム要点 TL;DR

+ +- **戦略**: 明示スタック+カーソルポインタによる反復中順走査 +- **データ構造**: `list[TreeNode]`(スタック)、`Optional[TreeNode]`(カーソル) +- **フェーズ**: + 1. **Ph1** — カーソルが非 `None` の間、左端までスタックに積む + 2. **Ph2** — スタックから `pop` → 値を記録 + 3. **Ph3** — カーソルを右子に移して Ph1 へ戻る +- **終了条件**: カーソルが `None` かつスタックが空 +- **時間計算量**: O(N) — 全ノードを一度だけ訪問 +- **空間計算量**: O(N) — 明示スタックの最大深さ(最悪:左偏木) +- **再帰なし**: コールスタックを消費しないため深い木でも安全 + +--- + +

図解

+ +### フローチャート + +```mermaid +flowchart TD + Start[Start: root = TreeNode or None] --> Guard{root is None} + Guard -- Yes --> RetEmpty[Return empty list] + Guard -- No --> Init[Init result stack cur=root] + Init --> Loop{cur is not None or stack not empty} + Loop -- No --> RetResult[Return result] + Loop -- Yes --> Ph1{cur is not None} + Ph1 -- Yes --> Push[stack.append cur] + Push --> MoveLeft[cur = cur.left] + MoveLeft --> Ph1 + Ph1 -- No --> Pop[node = stack.pop] + Pop --> Record[result.append node.val] + Record --> MoveRight[cur = node.right] + MoveRight --> Loop +``` + +> **読み方**: 左端まで積む(Ph1)→ pop して記録(Ph2)→ 右へ移動(Ph3)の3フェーズを繰り返す。カーソルとスタックが共に空になった時点で終了。 + +--- + +### データフロー図 + +```mermaid +graph LR + subgraph Input + A[root TreeNode or None] + end + subgraph Precheck + A --> B{None check} + B -- None --> C[Return empty list] + end + subgraph Core + B -- not None --> D[cursor = root] + D --> E[Ph1 push left spine] + E --> F[Ph2 pop and record val] + F --> G[Ph3 move to right child] + G --> E + end + subgraph Output + F --> H[result list int] + H --> I[Return result] + end +``` + +> **データの流れ**: `root` → Noneガード → カーソル初期化 → 3フェーズのループ → `result` リストとして返却。 + +--- + +### 具体的なトレース例 + +`root = [1, null, 2, 3]`(ツリー構造は下記) + +``` + 1 + \ + 2 + / + 3 +``` + +| ステップ | cur | stack(底→top) | result | 操作 | +| -------- | ------ | --------------- | --------- | --------------------------- | +| 初期 | 1 | [] | [] | — | +| Ph1 | None | [1] | [] | 1 を push、left=None で停止 | +| Ph2 | — | [] | [1] | pop→1、val=1 を記録 | +| Ph3 | 2 | [] | [1] | cur = right(2) | +| Ph1 | 3→None | [2, 3] | [1] | 2 push → 3 push | +| Ph2 | — | [2] | [1, 3] | pop→3、val=3 を記録 | +| Ph3 | None | [2] | [1, 3] | cur = right(None) | +| Ph2 | — | [] | [1, 3, 2] | pop→2、val=2 を記録 | +| Ph3 | None | [] | [1, 3, 2] | ループ終了 | + +**Output: `[1, 3, 2]`** ✅ + +--- + +

正しさのスケッチ

+ +### ループ不変条件 + +> 「`result` には、まだスタックに入っていない・未訪問でもない全ノードの値が中順で格納されている」 + +各反復で以下が保たれる: + +1. **Ph1 完了後**: スタックには「現在の左端パスのノード」が底→根方向で積まれている +2. **Ph2 完了後**: `pop` したノードは左部分木を全て処理済み → 自身の値を記録するのが中順で正しい +3. **Ph3 完了後**: `cur = node.right` により、右部分木が次の未処理対象になる + +### 網羅性 + +- **左部分木**: Ph1 のループで再帰的に処理 +- **根(自身)**: Ph2 の `pop` + `record` で処理 +- **右部分木**: Ph3 でカーソルを移動 → 次の Ph1 が処理 + +### 基底条件(終了性) + +- 各反復で必ず `stack.pop()` が1回実行される(スタックサイズが単調減少) +- `cur` が `None` になりスタックも空になれば `while` ループが終了 +- ノード数 N が有限なので、最大 N 回の pop で必ず終了する + +--- + +

計算量

+ +| 観点 | 値 | 理由 | +| -------------- | ---- | ------------------------------------------------------ | +| **時間計算量** | O(N) | 各ノードをスタックに push 1回・pop 1回の合計 2N 操作 | +| **空間計算量** | O(N) | スタックの最大深さ(最悪: N ノードが全て左に偏った木) | + +### アプローチ比較 + +| アプローチ | 時間 | 空間 | 可読性 | 安全性 | 備考 | +| --------------------------- | ---- | ---- | ------ | ------ | -------------------------------------------------- | +| **再帰 DFS** | O(N) | O(N) | ★★★ | △ | 再帰上限 1,000 でスタックオーバーフロー | +| **反復(明示スタック)** ✅ | O(N) | O(N) | ★★★ | ◎ | Follow-up 要件を満たす。本実装 | +| **Morris Traversal** | O(N) | O(1) | ★☆☆ | △ | ノードの `left` ポインタを一時書き換える副作用あり | + +> **選択理由**: Follow-up で反復解が明示要求されており、Morris は入力ツリーを一時変更する副作用があるため不採用。反復スタック実装が安全性・可読性・要件適合の三拍子を満たす最適解。 + +--- + +

Python 実装

+ +```python +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + # Pylance の型チェック用スタブ(実行時は LeetCode 環境の定義を使用) + 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: ... + + +# LeetCode 実行時フォールバック(TreeNode が未定義の環境向け) +try: + TreeNode # type: ignore[used-before-def] +except NameError: + + class TreeNode: # type: ignore[no-redef] + """最小フォールバック定義(__slots__ でメモリ最小化)""" + + __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: + """ + LeetCode 94 - Binary Tree Inorder Traversal + + 中順走査(左→根→右)を明示スタックによる反復で実装。 + Follow-up: 再帰を使わない反復解。 + """ + + def inorderTraversal(self, root: Optional[TreeNode]) -> list[int]: + """ + 二分木の中順走査を反復スタックで実行する。 + + Args: + root: 二分木の根ノード(None = 空木) + + Returns: + 中順走査の値リスト。空木の場合は空リスト []。 + + Complexity: + Time: O(N) - 全ノードを一度だけ訪問(push/pop 各 N 回) + Space: O(N) - 明示スタックの最大深さ(最悪: 左偏木で N) + """ + # ── ガード: 空木は即座に空リストを返す ────────────────────────── + if root is None: + return [] + + result: list[int] = [] # 中順走査の結果を蓄積 + stack: list[TreeNode] = [] # 明示スタック(非 None のみ格納) + cur: Optional[TreeNode] = root # 現在注目しているノード + + # cur(未訪問)または stack(保留)のいずれかが残る間ループ + while cur is not None or stack: + + # ── Ph1: 左端まで潜りながらスタックに積む ─────────────────── + while cur is not None: + stack.append(cur) # 自身と右部分木は後回し + cur = cur.left # 左へ進む + + # ── Ph2: スタック top を取り出して訪問 ────────────────────── + # ループ条件より stack が空でないことは保証済み + node: TreeNode = stack.pop() + result.append(node.val) # ← 中順で値を記録(左を全処理した後) + + # ── Ph3: 右部分木へカーソルを移す ─────────────────────────── + cur = node.right # None なら次ループで即 Ph2 へ + + return result +``` + +--- + +

CPython 最適化ポイント

+ +### list.append / list.pop の C 実装活用 + +```python +# CPython の list は動的配列。append/pop(-1) は均償 O(1) で C レイヤーで動作 +stack.append(cur) # C 実装: オーバーヘッド最小 +node = stack.pop() # C 実装: pop(-1) はリアロケーション不要 +``` + +> `collections.deque` は両端 O(1) が売りだが、末尾のみの操作なら `list` の方が CPython では高速。 + +### 属性アクセスのローカル変数キャッシュ + +```python +# N が大きい場合(本問題は N ≤ 100 なので省略可) +_append = result.append # 属性ルックアップを1回に削減 +_pop = stack.pop + +while cur is not None or stack: + while cur is not None: + stack.append(cur) + cur = cur.left + node = _pop() + _append(node.val) + cur = node.right +``` + +> N ≤ 100 の本問題では効果は誤差範囲だが、N が大きいユースケースへの応用時に有効。 + +### 再帰上限の回避 + +```python +# Python デフォルト再帰上限: sys.getrecursionlimit() = 1000 +# 深さ N の木で再帰 DFS を使うと N > 1000 でクラッシュ +# → 本実装の反復スタックは sys.setrecursionlimit() 不要で安全 +``` + +--- + +

エッジケースと検証観点

+ +| ケース | 入力 | 期待出力 | 本実装の挙動 | +| -------------------- | ------------------------ | --------------------- | ------------------------------------------------- | +| **空木** | `root = None` | `[]` | 冒頭ガードで即 `return []` | +| **単一ノード** | `root = [1]` | `[1]` | Ph1 で push、Ph2 で記録、Ph3 で `cur=None` → 終了 | +| **左偏木(深さ N)** | `[1,2,null,3,null,...]` | `[N,...,2,1]` | スタック深さ N まで積んでから順次 pop | +| **右偏木(深さ N)** | `[1,null,2,null,3]` | `[1,2,3]` | Ph1 は毎回 1 回のみ push(スタック深さ常に 1) | +| **完全二分木** | `[1,2,3,4,5,null,8,...]` | `[4,2,6,5,7,1,3,9,8]` | 全フェーズが均等に動作 | +| **負の値** | `root = [-100]` | `[-100]` | `node.val` をそのまま記録(符号処理なし) | +| **全ノード同値** | `[0,0,0,0,0]` | `[0,0,0,0,0]` | 値の重複は問題なし(インデックス管理不要) | + +### 静的型チェック(Pylance 対応確認点) + +- `root: Optional[TreeNode]` — `None` との Union を明示 +- `stack: list[TreeNode]` — 非 None のみ格納を型で保証 +- `cur: Optional[TreeNode]` — カーソルの nullable を型で追跡 +- `node: TreeNode` — `stack.pop()` 後に型を明示し `node.val` / `node.right` の属性アクセスを安全化 + +--- + +

FAQ

+ +**Q1. なぜ再帰ではなく反復で実装するのか?** + +A. Follow-up で明示要求されているほか、Python のデフォルト再帰上限(1,000)を超える深さの木でスタックオーバーフローが発生するリスクがある。反復実装は `sys.setrecursionlimit()` 変更なしに任意深さの木を安全に処理できる。 + +--- + +**Q2. Morris Traversal(O(1) 空間)を選ばなかった理由は?** + +A. Morris Traversal はノードの `left` ポインタを一時的に書き換えるため副作用がある(Pure function ではない)。本実装は入力ツリーへの書き込みを一切行わないため、関数の呼び出し前後でツリー構造が変化しない。可読性・安全性の観点からも反復スタック実装が優れている。 + +--- + +**Q3. `stack.pop()` の結果を `Optional[TreeNode]` にしなくていいのか?** + +A. `while` ループの条件 `cur is not None or stack` により、Ph2 に到達する時点でスタックが空でないことが保証される。ただし型推論上は `stack.pop()` の戻り値が `TreeNode` と確定しないため、`node: TreeNode = stack.pop()` と明示的な型注釈を付与している。 + +--- + +**Q4. `list` と `deque` どちらをスタックとして使うべきか?** + +A. 末尾への `append` / `pop(-1)` のみの操作なら CPython では `list` の方が高速。`deque` は両端操作が O(1) であるメリットが活きるのは `appendleft` / `popleft` を使う場合(BFS のキューなど)。スタック用途では `list` で十分。 + +--- + +**Q5. `from __future__ import annotations` が必要な理由は?** + +A. Python 3.11 以降で `Optional[TreeNode]` の前方参照を文字列評価(PEP 563)するため。`TreeNode` クラスが `TYPE_CHECKING` ブロック内にある場合でも、実行時に評価が遅延されることでランタイムエラーを回避できる。 diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html new file mode 100644 index 0000000..620a087 --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html @@ -0,0 +1,1570 @@ + + + + + + LeetCode 94 - Binary Tree Inorder Traversal + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+
+
+
+ 中順走査 +
+
左 → 根 → 右
+
+
+
O(N)
+
時間計算量
+
+
+
O(N)
+
空間計算量
+
+
+
+ 反復スタック +
+
再帰なし実装
+
+
+ +
+
+

📌 問題要約

+

+ 二分木の根ノード + root が与えられる。 + 中順走査(左→根→右) + でノードの値を収集し、リストとして返す。
+ Follow-up: + 再帰を使わない反復解を実装せよ。 +

+
+

+ Input: root = [1, null, 2, 3]
+ Output: [1, 3, 2] +

+
+
+
+

📏 制約

+
    +
  • 🔢 ノード数 N: 0 ≤ N ≤ 100
  • +
  • 🔢 値の範囲: −100 ≤ val ≤ 100
  • +
  • ⚠️ Python再帰上限: デフォルト 1,000(N>1000で危険)
  • +
  • ✅ 反復実装でスタックオーバーフロー回避
  • +
+
+
+
+ + +
+

+ ステップ解説 +

+
+
+ + +
+

+ 実装コード +

+ +
+ + + +
+ + +
+
from __future__ import annotations
+from typing import Optional
+
+
+# Definition for a binary tree node.
+class TreeNode:
+    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:
+    """
+    LeetCode 94 - Binary Tree Inorder Traversal
+    中順走査(左→根→右)を明示スタックによる反復で実装。
+
+    Time:  O(N) - 全ノードを一度だけ訪問
+    Space: O(N) - 明示スタックの最大深さ(最悪: 左偏木で N)
+    """
+
+    def inorderTraversal(self, root: Optional[TreeNode]) -> list[int]:
+        # ── ガード: 空木は即座に空リストを返す
+        if root is None:
+            return []
+
+        result: list[int] = []          # 中順走査の結果
+        stack: list[TreeNode] = []      # 明示スタック(非 None のみ格納)
+        cur: Optional[TreeNode] = root  # 現在注目しているノード
+
+        while cur is not None or stack:
+
+            # Ph1: 左端まで潜りながらスタックに積む
+            while cur is not None:
+                stack.append(cur)   # 右・自身は後回し
+                cur = cur.left      # 左へ進む
+
+            # Ph2: スタック top を取り出して訪問
+            node: TreeNode = stack.pop()
+            result.append(node.val)  # ← 中順で値を記録
+
+            # Ph3: 右部分木へカーソルを移す
+            cur = node.right  # None なら次ループで即 Ph2 へ
+
+        return result
+
+ + + + + + +
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + + + + + + 開始 + + + + + + + + 初期化 + + + + result=[] stack=[] cur=root + + + + + + + + cur != None + + + または stack 非空? + + + + + + No + + + + + + + + Yes + + + + + + Ph1: cur != None? + + + (左端まで潜る) + + + + + + Yes + + + + + スタックに積む + + + + stack.append(cur) cur = cur.left + + + + + + + + 繰り返し + + + + + + + + No + + + + + + + + Ph2: スタックから取り出して訪問 + + + + node = stack.pop() + + + result.append(node.val) ← 中順で記録 + + + + + + + + Ph3: 右部分木へ移動 + + + + cur = node.right + + + + + + + + + ループ継続 + + + + + + + + + 結果を返す + + + + return result # list[int] + + + + + + + 終了 + + +
+ +
+

+ フローの説明:
+ 1. 初期化: result・stack・curを初期設定する。
+ 2. ループ条件: + curが非Noneまたはstackが空でない間、3フェーズを繰り返す。
+ 3. Ph1(緑): + curが非Noneの間、左端まで潜りながらスタックに積む(ループバック=紫矢印)。
+ 4. Ph2(青): スタックからpopし、val + を結果リストに中順で記録する。
+ 5. Ph3(紫): cur = node.right + に移動し、ループ条件へ戻る(紫矢印)。
+ 6. 終了(赤): curとstackが共に空になったら結果を返す。 +

+
+
+ + +
+

+ 計算量分析 +

+ +
+
+
O(N)
+
時間計算量
+

+ 全ノードをスタックに push 1回・pop 1回の合計 2N 操作。定数倍を無視すると + O(N)。 +

+
+
+
O(N)
+
空間計算量
+

+ 明示スタックの最大深さ。最悪ケースは N + ノードが全て左に偏った木(スタック深さ = N)。 +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ アプローチ + + 時間 + + 空間 + + 可読性 + + 安全性 +
+ ✅ 反復(明示スタック) + O(N)O(N)★★★ + ◎ +
+ 再帰 DFS + O(N)O(N)★★★ + △ ※ +
+ Morris Traversal + O(N)O(1)★☆☆ + △ 副作用 +
+

+ ※ Python デフォルト再帰上限 1,000 でスタックオーバーフローリスクあり +

+
+
+ + +
+ LeetCode 94 — Binary Tree Inorder Traversal | 反復スタック実装解説 +
+
+ + + + + + + + diff --git a/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html new file mode 100644 index 0000000..8cc253e --- /dev/null +++ b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html @@ -0,0 +1,1570 @@ + + + + + + LeetCode 94 - Binary Tree Inorder Traversal + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+
+
+
+ 中順走査 +
+
左 → 根 → 右
+
+
+
O(N)
+
時間計算量
+
+
+
O(N)
+
空間計算量
+
+
+
+ 反復スタック +
+
再帰なし実装
+
+
+ +
+
+

📌 問題要約

+

+ 二分木の根ノード + root が与えられる。 + 中順走査(左→根→右) + でノードの値を収集し、リストとして返す。
+ Follow-up: + 再帰を使わない反復解を実装せよ。 +

+
+

+ Input: root = [1, null, 2, 3]
+ Output: [1, 3, 2] +

+
+
+
+

📏 制約

+
    +
  • 🔢 ノード数 N: 0 ≤ N ≤ 100
  • +
  • 🔢 値の範囲: −100 ≤ val ≤ 100
  • +
  • ⚠️ Python再帰上限: デフォルト 1,000(N>1000で危険)
  • +
  • ✅ 反復実装でスタックオーバーフロー回避
  • +
+
+
+
+ + +
+

+ ステップ解説 +

+
+
+ + +
+

+ 実装コード +

+ +
+ + + +
+ + +
+
from __future__ import annotations
+from typing import Optional
+
+
+# Definition for a binary tree node.
+class TreeNode:
+    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:
+    """
+    LeetCode 94 - Binary Tree Inorder Traversal
+    中順走査(左→根→右)を明示スタックによる反復で実装。
+
+    Time:  O(N) - 全ノードを一度だけ訪問
+    Space: O(N) - 明示スタックの最大深さ(最悪: 左偏木で N)
+    """
+
+    def inorderTraversal(self, root: Optional[TreeNode]) -> list[int]:
+        # ── ガード: 空木は即座に空リストを返す
+        if root is None:
+            return []
+
+        result: list[int] = []          # 中順走査の結果
+        stack: list[TreeNode] = []      # 明示スタック(非 None のみ格納)
+        cur: Optional[TreeNode] = root  # 現在注目しているノード
+
+        while cur is not None or stack:
+
+            # Ph1: 左端まで潜りながらスタックに積む
+            while cur is not None:
+                stack.append(cur)   # 右・自身は後回し
+                cur = cur.left      # 左へ進む
+
+            # Ph2: スタック top を取り出して訪問
+            node: TreeNode = stack.pop()
+            result.append(node.val)  # ← 中順で値を記録
+
+            # Ph3: 右部分木へカーソルを移す
+            cur = node.right  # None なら次ループで即 Ph2 へ
+
+        return result
+
+ + + + + + +
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + + + + + + 開始 + + + + + + + + 初期化 + + + + result=[] stack=[] cur=root + + + + + + + + cur != None + + + または stack 非空? + + + + + + No + + + + + + + + Yes + + + + + + Ph1: cur != None? + + + (左端まで潜る) + + + + + + Yes + + + + + スタックに積む + + + + stack.append(cur) cur = cur.left + + + + + + + + 繰り返し + + + + + + + + No + + + + + + + + Ph2: スタックから取り出して訪問 + + + + node = stack.pop() + + + result.append(node.val) ← 中順で記録 + + + + + + + + Ph3: 右部分木へ移動 + + + + cur = node.right + + + + + + + + + ループ継続 + + + + + + + + + 結果を返す + + + + return result # list[int] + + + + + + + 終了 + + +
+ +
+

+ フローの説明:
+ 1. 初期化: result・stack・curを初期設定する。
+ 2. ループ条件: + curが非Noneまたはstackが空でない間、3フェーズを繰り返す。
+ 3. Ph1(緑): + curが非Noneの間、左端まで潜りながらスタックに積む(ループバック=紫矢印)。
+ 4. Ph2(青): スタックからpopし、val + を結果リストに中順で記録する。
+ 5. Ph3(紫): cur = node.right + に移動し、ループ条件へ戻る(紫矢印)。
+ 6. 終了(赤): curとstackが共に空になったら結果を返す。 +

+
+
+ + +
+

+ 計算量分析 +

+ +
+
+
O(N)
+
時間計算量
+

+ 全ノードをスタックに push 1回・pop 1回の合計 2N 操作。定数倍を無視すると + O(N)。 +

+
+
+
O(N)
+
空間計算量
+

+ 明示スタックの最大深さ。最悪ケースは N + ノードが全て左に偏った木(スタック深さ = N)。 +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ アプローチ + + 時間 + + 空間 + + 可読性 + + 安全性 +
+ ✅ 反復(明示スタック) + O(N)O(N)★★★ + ◎ +
+ 再帰 DFS + O(N)O(N)★★★ + △ ※ +
+ Morris Traversal + O(N)O(1)★☆☆ + △ 副作用 +
+

+ ※ Python デフォルト再帰上限 1,000 でスタックオーバーフローリスクあり +

+
+
+ + +
+ LeetCode 94 — Binary Tree Inorder Traversal | 反復スタック実装解説 +
+
+ + + + + + + + diff --git a/public/index.html b/public/index.html index ef6224d..790eaa9 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

165 interactive lessons across 6 domains

+

166 interactive lessons across 6 domains

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

- + @@ -475,6 +475,7 @@

  • 🧩LeetCode 7: Reverse Integer - 文字列反転法Algorithm/Other/leetcode/7. Reverse Integer/claude/README.html
  • 🧩LeetCode 88 – Merge Sorted ArrayAlgorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html
  • 🧩LeetCode 93: Restore IP Addresses - DFS + 枝刈り解説Algorithm/Backtracking/leetcode/93. Restore IP Addresses/Claude/README.html
  • +
  • 🧩LeetCode 94 - Binary Tree Inorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html
  • 🧩LeetCode 96: Unique Binary Search Trees - カタラン数解説Algorithm/BinarySearch/leetcode/96. Unique Binary Search Trees/claude 4.5 sonnet/README_react.html
  • 🧩LeetCode 97: Interleaving String - 1D DP解説Algorithm/DynamicProgramming/leetcode/97. Interleaving String/Claude Sonnet 4.5/README_React.html
  • 🧩LeetCode 98: Validate Binary Search TreeAlgorithm/BinarySearch/leetcode/98. Validate Binary Search Tree/Claude Sonnet 4.5/README_react.html
  • @@ -647,6 +648,7 @@

  • 🧩LeetCode 7: Reverse Integer - 文字列反転法Algorithm/Other/leetcode/7. Reverse Integer/claude/README.html
  • 🧩LeetCode 88 – Merge Sorted ArrayAlgorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html
  • 🧩LeetCode 93: Restore IP Addresses - DFS + 枝刈り解説Algorithm/Backtracking/leetcode/93. Restore IP Addresses/Claude/README.html
  • +
  • 🧩LeetCode 94 - Binary Tree Inorder TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/94. Binary Tree Inorder Traversal/README_react.html
  • 🧩LeetCode 96: Unique Binary Search Trees - カタラン数解説Algorithm/BinarySearch/leetcode/96. Unique Binary Search Trees/claude 4.5 sonnet/README_react.html
  • 🧩LeetCode 97: Interleaving String - 1D DP解説Algorithm/DynamicProgramming/leetcode/97. Interleaving String/Claude Sonnet 4.5/README_React.html
  • 🧩LeetCode 98: Validate Binary Search TreeAlgorithm/BinarySearch/leetcode/98. Validate Binary Search Tree/Claude Sonnet 4.5/README_react.html
  • @@ -813,7 +815,7 @@

    🧪 - Generated on 2026-03-14 + Generated on 2026-03-18
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +

    + アルゴリズム概要 +

    + +
    +

    + 💡 + この問題を一言で言うと:「2本の二分木が、形も値もまったく同じかどうかを確認する問題」 +

    +

    + 二分木(=各ノードが左と右に高々1つずつ子を持つ木構造)が2本与えられます。 + 「同じ木」とは、すべての対応するノードが同じ値を持ち、かつ木の形(どこに子がいるか)も完全に一致することです。 + 単純に見えますが、「木の形の比較」という点で少し考える必要があります。 +

    +
    + +
    +

    + ⚠️ なぜ単純な方法では解けないのか +

    +
      +
    • + null(何もない)の扱いが難しい:片方の木にはノードがあり、もう片方には何もない(null)という場合を正確に区別しなければならない +
    • +
    • + 全ノードを調べる必要がある:根(ルート)の値が同じでも、葉(末端)の値や位置が違えば「異なる木」になる。部分的な確認では不十分 +
    • +
    +
    + +
    +
    +
    O(n)
    +
    時間計算量
    +
    +
    +
    O(h)
    +
    空間計算量
    +
    +
    +
    + 再帰DFS +
    +
    アルゴリズム
    +
    +
    +
    Easy
    +
    難易度
    +
    +
    + + +
    +
    +

    + 例1 → true +

    +
    +p: [1,2,3]    q: [1,2,3]
    +    1              1
    +   / \            / \
    +  2   3          2   3
    +

    形も値もすべて同じ → true ✅

    +
    +
    +

    + 例2 → false +

    +
    +p: [1,2]      q: [1,null,2]
    +    1              1
    +   /                \
    +  2                  2
    +

    + 値は同じでも位置(左 vs 右)が違う → false ❌ +

    +
    +
    +

    + 例3 → false +

    +
    +p: [1,2,1]    q: [1,1,2]
    +    1              1
    +   / \            / \
    +  2   1          1   2
    +

    + 左右の値が入れ替わっている → false ❌ +

    +
    +
    + +
    +

    📌 制約

    +
      +
    • + 両方の木のノード数は + 0 以上 + 100 以下 +
    • +
    • + -10⁴ ≤ Node.val ≤ 10⁴ +
    • +
    +
    +
    + + +
    +

    + ステップバイステップ解説 +

    +

    + 再帰DFSがどのように動くかを4つのステップで確認しましょう。 ▶ Play + ボタンで自動的に進めることもできます。 +

    +
    +
    + + +
    +

    + Python 実装 +

    + +
    +

    + 📋 このコードの構造(先に全体像を把握しよう) +

    +
      +
    1. + 両方が + None + かを確認 → 同じ葉の先端なら + True +
    2. +
    3. + 片方だけ + None + かを確認 → 構造が違うので + False +
    4. +
    5. + 両方の値(p.val != q.val)が違うなら + False +
    6. +
    7. + 左の子木・右の子木を再帰で比較し、両方一致なら + True +
    8. +
    +
    + +
    class Solution(object):
    +    def isSameTree(self, p, q):
    +        """
    +        :type p: Optional[TreeNode]
    +        :type q: Optional[TreeNode]
    +        :rtype: bool
    +        """
    +        # ── ① 両方 None のとき ──────────────────────────
    +        # 葉ノードのさらに下(何もない場所)に両方とも到達した。
    +        # 「どちらにも子がない」=構造が一致している → True
    +        # `is None` を使うのが Pythonic(Pythonらしい慣用的な書き方)
    +        if p is None and q is None:
    +            return True
    +
    +        # ── ② 片方だけ None のとき ──────────────────────
    +        # ①で「両方 None」はすでに return 済み。
    +        # ここに来るのは「どちらか一方だけ None」の場合のみ。
    +        # 片方にノードがあり、片方にない = 構造が違う → False
    +        if p is None or q is None:
    +            return False
    +
    +        # ── ③ 値の比較 ───────────────────────────────────
    +        # ①②を通過した時点で p も q も None でないことが確定。
    +        # pylance もここでは p・q を TreeNode として認識する
    +        # (型の絞り込み = Type Narrowing と呼ばれる仕組み)。
    +        if p.val != q.val:
    +            return False
    +
    +        # ── ④ 左右の子木を再帰で比較 ────────────────────
    +        # 根の値が一致したので、次は左・右の子木を同じ手順で比較する。
    +        # `and` の短絡評価(左が False なら右は実行しない)で
    +        # 不一致が見つかった時点で即座に False を返せる。
    +        return (
    +            self.isSameTree(p.left, q.left)
    +            and self.isSameTree(p.right, q.right)
    +        )
    + +
    +

    + ▶ 入力例 p=[1,2] / q=[1,null,2] での動作トレース +

    +
    +呼び出し①: isSameTree(Node(1), Node(1))
    +  → ① 両方非 None → パス
    +  → ② どちらも非 None → パス
    +  → ③ 1 == 1 → パス(値が等しいので続ける)
    +  → ④ 左の子を比較するために再帰呼び出し
    +
    +呼び出し②: isSameTree(Node(2), None)   ← p.left=Node(2), q.left=None
    +  → ① p は非 None → パス(両方 None ではない)
    +  → ② p は非 None だが q は None → return False ← ここで終了!
    +
    +呼び出し①に戻る:
    +  → isSameTree(p.left, q.left) = False
    +  → and の短絡評価:False and ... → 右辺の再帰は実行されない
    +  → return False
    +
    +最終結果: False ✅
    +
    +
    + + +
    +

    + 処理フローチャート +

    + +
    +

    + 🗺️ フローチャートの読み方 +

    +
    +
    + + + + 楕円(緑)= 開始・終了 +
    +
    + + + + 四角(青)= 処理ステップ +
    +
    + + + + ひし形(黄)= 条件分岐 +
    +
    + 緑=はい + 赤=いいえ +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + 開始: isSameTree(p, q) + + + + + + + + + p と q は + + + どちらも None? + + + + + + はい + + + + + + True + + + + + + いいえ + + + + + + どちらか一方だけ + + + None? + + + + + + はい + + + + False + + + + + + いいえ + + + + + + p.val ≠ q.val + + + (値が違う)? + + + + + + はい + + + + False + + + + + + いいえ + + + + + + isSameTree(p.left, q.left) を再帰呼び出し + + + + + + + + + 左の結果は + + + True? + + + + + + いいえ + + + + False + + + + + + はい + + + + + + isSameTree(p.right, q.right) を再帰呼び出し + + + + + + + + + 右の結果は + + + True? + + + + + + いいえ + + + + False + + + + + + はい + + + + + + 終了: True を返す + + + + + + + + 再帰ループ(子ノードへ) + + +
    + +
    +

    + 🔎 入力例 p=[1,2,3] / q=[1,2,3] でのフロー追跡 +

    +
      +
    1. 「開始」ノード → isSameTree(Node(1), Node(1)) を受け取る
    2. +
    3. + 「どちらも None?」ノード → 両方非 None → + いいえ の経路へ +
    4. +
    5. + 「片方だけ None?」ノード → どちらも非 None → + いいえ の経路へ +
    6. +
    7. + 「p.val ≠ q.val?」ノード → 1 == 1 → + いいえ の経路へ(値が等しいので続ける) +
    8. +
    9. + 「左の子木を再帰比較」→ isSameTree(Node(2), Node(2)) + を再帰呼び出し(さらに深く潜る) +
    10. +
    11. 「左の結果 True?」→ 左の子木も一致 → はい の経路へ
    12. +
    13. 「右の子木を再帰比較」→ isSameTree(Node(3), Node(3)) を再帰呼び出し
    14. +
    15. 「右の結果 True?」→ 右の子木も一致 → はい の経路へ
    16. +
    17. 「終了」ノード → True を返す ✅
    18. +
    +
    +
    + + +
    +

    + 計算量分析 +

    + +
    +

    + 📖 Big-O + 記法の読み方(入力サイズが大きくなるにつれて処理時間がどう増えるかの目安) +

    +
    +
    +
    O(1)
    +
    + 常に一定
    例:辞書の直接引き +
    +
    +
    +
    O(n)
    +
    + 入力に比例
    例:リストを1回走査 +
    +
    +
    +
    O(n log n)
    +
    + n より少し多い
    例:ソートアルゴリズム +
    +
    +
    +
    O(n²)
    +
    + 入力の2乗
    例:二重ループ総当たり +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    観点計算量条件
    時間計算量O(n) + n = 2つの木の総ノード数。全ノードを最大1回訪問 +
    空間計算量(平均)O(log n) + 平衡二分木の場合(高さ h ≈ log₂ n) +
    空間計算量(最悪)O(n) + 一本道の木(高さ = ノード数)の場合 +
    本問題での実際O(100) + ノード数 ≤ 100 の制約により事実上定数 +
    +
    + +
    +

    + 🔍 なぜこの計算量になるのか +

    +

    + 時間計算量 O(n):「2つの木が同じかどうか」を確認するには、すべてのノードを少なくとも1回は調べなければなりません。再帰DFSは各ノードをちょうど1回だけ訪問するため、n + ノードに対して O(n) の操作で済みます。
    + 空間計算量 O(h):再帰呼び出しはコールスタック(=関数呼び出しの積み重ね)にメモリを使います。一番深くまで潜ったとき(葉ノードに到達したとき)の積み重ねの深さが木の高さ + h なので、O(h) + のメモリが必要です。追加のデータ構造(リストやキューなど)は一切使わないため、スタック以外のメモリは + O(1) です。 +

    +
    + + +
    +

    📊 アプローチ別比較

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    アプローチ時間空間特徴
    + ✅ 再帰DFS(採用) + O(n)O(h) + コードが最もシンプル。問題の定義と1対1対応 +
    + 反復DFS(スタック) + O(n)O(h) + list をスタック代わりに使用。再帰を使わない +
    + 反復BFS(キュー) + O(n) + O(n) + + deque 使用。常に O(n) メモリを消費して不利 +
    +
    +
    +
    + + +
    +

    + 📖 用語集 +

    +

    + このページで登場した専門用語をまとめました。分からない言葉が出てきたときに参照してください。 +

    +
    +
    + + None(ナン) + +
    + Pythonで「何もない」を表す特別な値。他の言語の + null + に相当します。 二分木では、子がいないノードの + left や + right + が + None + になります。 比較する際は + == None + ではなく + is None + を使うのが Pythonic です。 +
    +
    + +
    + + DFS(深さ優先探索 / + Depth-First Search) + +
    + 木やグラフを「根から葉まで深く潜ってから戻る」順番で探索する手法。 + 迷路を解くとき「行き止まりに当たるまでまっすぐ進み、行き止まりになったら戻って別の道を試す」のと同じ考え方です。 + 今回の問題では再帰関数が自動的に DFS の順序でノードを訪問します。 +
    +
    + +
    + + コールスタック(Call + Stack) + +
    + 関数を呼び出すたびに「呼び出し情報」を積み上げていくメモリ領域。 + お皿の山積みに例えると、新しい関数呼び出しのたびにお皿を1枚重ね、関数が終了するとお皿を1枚取り除きます。 + 再帰が深くなるほどお皿が積み重なり、メモリを消費します。これが空間計算量 + O(h) の理由です。 +
    +
    + +
    + + 再帰(Recursion) + +
    + 関数が自分自身を呼び出す仕組み。「木の比較」のように「同じ問題が小さいサイズで繰り返される」構造に特に適しています。 + 必ず「基底条件(これ以上深く行かない条件)」を設定しないと無限ループになるので注意が必要です。 + 今回の基底条件は + p is None and q is None + のときに + True + を返す部分です。 +
    +
    + +
    + + + 短絡評価(Short-Circuit Evaluation) + +
    + A and B + の A が + False + なら B を評価しない・ + A or B + の A が + True + なら B を評価しない仕組み。 今回のコードでは + isSameTree(p.left, q.left) and isSameTree(p.right, q.right) + において、 + 左の子木が一致しなければ右の再帰は実行されません。不必要な処理を省いて効率化できます。 +
    +
    + +
    + + 二分木(Binary + Tree) + +
    + 各ノード(節点)が高々2つの子(左の子・右の子)を持つ木構造のこと。 + 家系図に例えると、親が最大2人の子を持てる構造です。 今回の問題の + TreeNode + クラスはこの構造を + left と + right + の2つの参照で表現しています。 +
    +
    + +
    + + 平衡二分木(Balanced + Binary Tree) + +
    + 左右の子木の高さの差が小さい、バランスの取れた二分木のこと。 n + 個のノードを持つ平衡二分木の高さは約 log₂ n になります。 例えば 1000 + ノードなら高さは約 10 です。 逆に「一本道」の木(チェーン状)は高さが n + になり、最悪ケースの空間計算量 O(n) に相当します。 +
    +
    + +
    + + + Pythonic(パイソニック) + +
    + Pythonらしい、慣用的な書き方のこと。Pythonコミュニティが「この書き方が自然で読みやすい」と考えるスタイルを指します。 + 例えば + x == None + より + x is None、 + len(lst) == 0 + より + not lst + が Pythonic とされています。 +
    +
    +
    +
    + + +
    +

    LeetCode 100 — Same Tree | 再帰DFS による O(n) 実装解説

    +

    Python 3 · 初学者向け解説ページ

    +
    +
    + + + + + + + + diff --git a/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Python.md b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Python.md new file mode 100644 index 0000000..3bdb866 --- /dev/null +++ b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Python.md @@ -0,0 +1,241 @@ +## 1. 問題分析結果 + +> 💡 **初学者向け補足**:この問題を一言で言うと「2本の二分木が、形も値もまったく同じかどうかをPythonの再帰で確認する問題」です。 +> +> **Pythonで解く際に特に気をつけるべきCPython特有の注意点**:LeetCodeが提供する `TreeNode` は通常のPythonクラスです。TypeScriptやRustのような複雑なラッパー型は不要で、`Optional[TreeNode]` という型ヒントで「ノードまたは `None`」を表現できます。Python の再帰はデフォルトで深さ1000までしか許可されていませんが(`sys.setrecursionlimit` で変更可)、本問題の制約はノード数100以下なので問題ありません。また `None` の比較は `is None` を使うのが Pythonic(=Pythonらしい書き方)です。 + +#### 競技プログラミング視点 + +- **制約分析**:ノード数 ≦ 100 → O(n) で十分。再帰深度も最大100なのでスタックオーバーフローの心配なし +- **最速手法**:再帰DFS(深さ優先探索)。Pythonの関数呼び出しオーバーヘッドはあるが、n≦100の制約では無視できる +- **メモリ最小化**:再帰スタックのみ使用。追加のデータ構造(`deque` や `list`)は不要 + +#### 業務開発視点 + +- **型安全設計**:`Optional[TreeNode]` を引数・戻り値に明示し、pylanceが `None` を渡した場合の型エラーを検出できるようにする +- **エラーハンドリング**:制約上 `val` は必ず `int` なので型エラーは現実的ではないが、`None` アクセスを `if` で防ぐことが重要 +- **可読性**:`if p is None and q is None` のように意図が明確な条件式を使う + +#### Python特有分析 + +- **データ構造選択**:追加のデータ構造不要。再帰コールスタックのみ +- **標準ライブラリ活用度**:今回は `typing.Optional` のみ使用。シンプルな問題ほどPython組み込みの強みが活きる +- **CPython最適化度**:再帰は Pure Python だが、n≦100 の制約では実用上問題なし + +> 📖 **このセクションで登場した用語** +> +> - **CPython**:最も広く使われるPythonの実装。C言語で書かれており、組み込み関数の多くがC実装のため高速 +> - **`Optional[T]`**:「T または None のどちらか」を表す型ヒント。`Optional[TreeNode]` は「TreeNodeかNoneか」 +> - **Pythonic**:Pythonらしい、慣用的な書き方のこと。`is None` は `== None` より意図が明確でPythonicとされる +> - **再帰深度制限**:CPythonはデフォルトで関数の入れ子呼び出しを1000回までに制限している。`sys.getrecursionlimit()` で確認できる + +--- + +## 2. 採用アルゴリズムと根拠 + +> 💡 **初学者向け補足**:同じ問題でも解き方は複数あります。「Python的にどの書き方が速いか・読みやすいか」という観点で比較します。C実装の組み込み関数を使えるかどうかも重要な判断基準です。 + +| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | +| -------------------- | ---------- | ---------- | ---------------- | ------ | ------------------------------- | ----------------------------- | -------------------------------- | +| **再帰DFS** | O(n) | O(h) | 低 | ★★★ | `typing` のみ | 不適(Pure Python再帰) | 問題構造と直接対応、最もシンプル | +| **反復DFS(stack)** | O(n) | O(h) | 中 | ★★☆ | `collections` 不要・`list` のみ | 適(list操作はC実装) | `append`/`pop` は O(1) | +| **反復BFS(deque)** | O(n) | O(n) | 中 | ★★☆ | `collections.deque` | 適(`deque.popleft` が O(1)) | 常に O(n) メモリを消費 | + +**選択理由**:再帰DFSを採用します。`list` の `pop()` はC実装で速いですが、今回は n≦100 の制約なので速度差は無意味です。「2つの木が同じ ⟺ 根の値が同じ かつ 左の子木が同じ かつ 右の子木が同じ」という問題の定義がそのまま再帰の構造に対応しており、コードの意図が最も明確に伝わるためです。 + +**Python最適化戦略**:`and` の短絡評価(=左辺が `False` なら右辺を評価しない仕組み)を活用して、不一致が見つかった時点で即座に `False` を返します。 + +> 📖 **このセクションで登場した用語** +> +> - **短絡評価**:`A and B` の A が `False` なら B を評価しない仕組み。`False and 重い処理()` は重い処理を実行しない +> - **`deque.popleft()`**:`deque`(両端キュー)の先頭要素を O(1) で取り出す操作。`list.pop(0)` は O(n) なので大きなデータには不向き +> - **Pure Python**:C言語ではなくPythonで書かれたコード。CPythonの組み込み関数より遅い傾向がある + +--- + +## 3. 実装パターン + +> コードの骨格: +> +> 1. 両方が `None` なら → 同じ(`True`) +> 2. 片方だけ `None` なら → 違う(`False`) +> 3. 値が違うなら → 違う(`False`) +> 4. 値が同じなら → 左の子木・右の子木を再帰で比較 + +--- + +【業務開発版を使う場面】 +チームで長期間メンテナンスするプロダクションコードに向きます。型ヒントを丁寧に書き、pylanceが `None` の不正アクセスを実行前に検出できる構造にしています。コメントや docstring で意図を伝えることを優先します。 + +```python +from typing import Optional + + +class Solution: + def isSameTree( + self, + p: Optional["TreeNode"], + q: Optional["TreeNode"], + ) -> bool: + """ + 2つの二分木が構造・値ともに完全に一致するか判定する(業務開発版) + + Args: + p: 比較元の木のルートノード(または None) + q: 比較先の木のルートノード(または None) + + Returns: + 2つの木が同一なら True、異なれば False + + Complexity: + Time: O(n) n = 総ノード数(全ノードを最大1回訪問) + Space: O(h) h = 木の高さ(再帰コールスタックの深さ) + 平均 O(log n)、最悪(一本道の木)O(n) + """ + # ── ① 両方 None のとき ────────────────────────────────── + # 両方が None ということは「どちらにも子が存在しない」 + # = 葉ノードの先端に到達し、構造が一致している → True + # `is None` を使うのが Pythonic。`== None` は非推奨(pylance 警告あり) + if p is None and q is None: + return True + + # ── ② 片方だけ None のとき ────────────────────────────── + # ①で「両方 None」は処理済みなので、ここは「どちらか一方だけ None」の場合 + # 構造が異なる → False + if p is None or q is None: + return False + + # ── ③ 値の比較 ─────────────────────────────────────────── + # ここに到達した時点で p も q も None でないことが確定している。 + # pylance もここでは p・q を TreeNode として認識する(型の絞り込み)。 + # 値が違えば木の内容が異なる → False + if p.val != q.val: + return False + + # ── ④ 左右の子木を再帰で比較 ──────────────────────────── + # 「p と q が同じ木」 ⟺ + # 「根の値が同じ」かつ「左の子木が同じ」かつ「右の子木が同じ」 + # `and` の短絡評価により、左が False なら右の再帰は実行されない。 + # 不一致が見つかった時点で即座に False を返せるので効率的。 + left_same: bool = self.isSameTree(p.left, q.left) + right_same: bool = self.isSameTree(p.right, q.right) + + return left_same and right_same +``` + +--- + +【競技プログラミング版を使う場面】 +LeetCodeなど制限時間内に正解を出すことが目的のコードに向きます。エラーハンドリングや丁寧なコメントを省き、1つの `return` 文で処理全体を表現します。 + +```python +from typing import Optional + + +class Solution: + def isSameTree( + self, + p: Optional["TreeNode"], + q: Optional["TreeNode"], + ) -> bool: + # 両方 None → True、片方だけ None → False を1行で処理。 + # `p and q` は p が None(= Falsy)なら False を返す短絡評価。 + # `not (p or q)` は「どちらも None」のとき True になる。 + if not p and not q: + return True + # 片方だけ None、または値が違う → False + if not p or not q or p.val != q.val: + return False + # 左右の子木を再帰で比較。and の短絡評価で早期終了。 + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) +``` + +--- + +> 💡 **コードの動作トレース**(Example 2: `p=[1,2]`, `q=[1,null,2]`) +> +> ``` +> 呼び出し①: isSameTree(p=Node(1), q=Node(1)) +> → ① p,q ともに非 None → パス +> → ② どちらも非 None → パス +> → ③ 1 == 1 → パス +> → ④ left_same = isSameTree(p.left, q.left) を呼び出す +> +> 呼び出し②: isSameTree(p=Node(2), q=None) +> → ① p は非 None → パス +> → ② q は None → return False ← ここで終了! +> +> 呼び出し①に戻る: +> → left_same = False +> → False and ... → and の短絡評価で right_same の再帰は実行されない +> → return False +> +> 全体の結果: False +> ``` + +--- + +> 📖 **このセクションで登場した用語** +> +> - **型の絞り込み(Type Narrowing)**:`if p is None: return` の後ではpylanceが「pはNoneではない」と自動的に判断してくれる仕組み +> - **Falsy**:`bool(x)` が `False` になる値。Pythonでは `None`・`0`・空リスト `[]`・空文字 `""` などが該当する +> - **短絡評価**:`A and B` の A が `False` なら B を評価しない・`A or B` の A が `True` なら B を評価しない仕組み +> - **`Optional["TreeNode"]`**:文字列で型名を書く「前方参照」。クラス定義より前に型名を使いたいときに `"TreeNode"` と文字列にする + +--- + +## 4. 検証 + +> エッジケースのテストは、アルゴリズムが「ふつうの入力」だけでなく「極端な入力」でも正しく動くかを確かめるためのものです。 + +| ケース | p | q | 期待出力 | 確認ポイント | +| ---------------- | --------- | ------------ | -------- | ------------------------- | +| 両方空 | `None` | `None` | `True` | ① の `None and None` 処理 | +| 片方空 | `[1]` | `None` | `False` | ② の片側 `None` 処理 | +| 構造同一・値同一 | `[1,2,3]` | `[1,2,3]` | `True` | 全ノード比較の正常系 | +| 構造異なる | `[1,2]` | `[1,null,2]` | `False` | 片方だけ子がある場合 | +| 値異なる | `[1,2,1]` | `[1,1,2]` | `False` | ③ の値比較(左右反転) | +| 1ノードのみ同値 | `[1]` | `[1]` | `True` | 葉ノード単体の比較 | +| 1ノードのみ異値 | `[1]` | `[2]` | `False` | 根だけで即 `False` | + +> 📖 **このセクションで登場した用語** +> +> - **エッジケース**:空の入力・要素1つ・最大サイズ入力など、境界的な条件のこと +> - **正常系**:想定通りの入力で期待通りの出力が得られることを確認するテスト +> - **前方参照**:クラスや関数の定義より前に、その型名を文字列 `"ClassName"` で書くPythonの慣用的な書き方。Python 3.10以降は `from __future__ import annotations` で省略できる + +```python +# Runtime 0 ms +# Beats 100.00% +# Memory 12.55 MB +# Beats 47.63% +class Solution(object): + def isSameTree(self, p, q): + """ + :type p: Optional[TreeNode] + :type q: Optional[TreeNode] + :rtype: bool + """ + # ── ① 両方 None のとき ────────────────────────────────── + # 葉ノードの先端に到達し、構造が一致している → True + # `is None` を使うのが Pythonic(`== None` より意図が明確) + if p is None and q is None: + return True + + # ── ② 片方だけ None のとき ────────────────────────────── + # ①で「両方 None」は処理済みなので、ここは「どちらか一方だけ None」 + # 構造が異なる → False + if p is None or q is None: + return False + + # ── ③ 値の比較 ─────────────────────────────────────────── + # 両方 None でないことが確定しているので .val に安全にアクセスできる + # 値が違えば木の内容が異なる → False + if p.val != q.val: + return False + + # ── ④ 左右の子木を再帰で比較 ──────────────────────────── + # `and` の短絡評価により、左が False なら右の再帰は実行されない + # 不一致が見つかった時点で即座に False を返せるので効率的 + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) +``` diff --git a/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Rust.md b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Rust.md new file mode 100644 index 0000000..b1331a3 --- /dev/null +++ b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_Rust.md @@ -0,0 +1,501 @@ +あなたは世界トップクラスのRustに特化したスペシャリストのエンジニアです。 + +# Rust コーディング問題 + +「以下のテンプレートに従い、解析~実装~検証までを回答してください。 +Rust Edition 2021、**Cargo不使用・標準ライブラリのみ** 外部クレート不可。回答は必ず"leetcodeでの回答フォーマット"で。」 +_テストコードは不要です!具体的な計測値、メモリの出力も不要です!_ + +--- + +## ✅ 初学者向け解説ポリシー(必須) + +**すべての解説において、以下の方針を徹底してください。** + +### 基本姿勢 + +- **「なぜそうするのか」を必ず先に説明する**。コードを示す前に、その目的・意図・背景を1〜3文で明記すること。 +- **専門用語を初めて使う際は、必ずカッコ内で平易な言葉での補足説明を付ける**。 + - 例:「所有権(=値を"誰が管理するか"をコンパイル時に決めるRust独自の仕組み)」 + - 例:「ゼロコスト抽象化(=便利な書き方をしても、手書きの低レベルコードと同じ速さになる性質)」 + - 例:「モノモーフィゼーション(=ジェネリクスを使った関数が、型ごとに専用のコードへ自動展開される仕組み)」 +- **難しい概念は具体的な日常的な例え話を使って説明する**。 + - 例:「所有権は"本の貸し出し"に似ています。本(値)は一度に1人(所有者)しか持てず、貸し出す(借用)ときはルールに従います」 + - 例:「`Result`は"成功か失敗かを必ず報告する宅配便"のようなものです。受け取り側は必ず結果を確認しなければなりません」 +- 「当然」「自明」「明らか」「簡単に」など、**初学者を置き去りにする表現は使わない**。 +- **Rust特有の概念(所有権・借用・ライフタイム)は他言語との比較を交えて説明する**。 + - 例:「JavaやPythonではガベージコレクタがメモリを管理しますが、Rustでは所有権ルールによってコンパイル時にメモリ管理を行います」 + +### ステップバイステップ解説の具体的なルール + +#### コードブロックへの行コメント + +- すべてのコードブロックにおいて、**各処理の行または数行ごとに日本語コメントを付ける**。 +- コメントは「何をしているか」だけでなく「なぜそうするか」「他の書き方ではダメな理由」まで書く。 + + ```rust + // &[T] は「スライスへの参照(借用)」で受け取る。 + // Vec で受け取ると呼び出し元から所有権を奪ってしまうため、 + // 読み取り専用の処理には &[T] を使うのがRustの慣習。 + fn algorithm(input: &[i32]) -> Result { + // is_empty() で空チェック。空のスライスに対して処理を続けると + // 後続の index アクセスでパニックする恐れがあるため最初に確認する。 + if input.is_empty() { + return Err(AlgorithmError::EmptyInput); + } + ``` + +#### 概念の段階的な積み上げ + +- 新しい概念を導入するときは、**「基礎 → 応用 → この問題での使い方」の3段階**で説明する。 + 1. **基礎**:その概念単体をシンプルな例で説明(他言語との比較も歓迎) + 2. **応用**:少し複雑な場面での使い方を説明 + 3. **この問題での使い方**:実際のコードでどう使っているかを示す + +#### アルゴリズムの図解・トレース + +- アルゴリズムの動きを説明する際は、**入力データが変化していく様子をステップごとに文字で図示する**。 + + ``` + 例)入力: [3, 1, 4, 1, 5] + Step 1: acc=3, x=1 → 3 > 1 なので acc=3 を維持 + Step 2: acc=3, x=4 → 4 > 3 なので acc=4 に更新 + Step 3: acc=4, x=1 → 4 > 1 なので acc=4 を維持 + Step 4: acc=4, x=5 → 5 > 4 なので acc=5 に更新 + 結果: 5 + ``` + +#### Rust固有の概念への補足(必須) + +以下の構文・概念を使う際は、**必ずその構文が「なぜRustに存在するのか」と「使うことで何が嬉しいか」をセットで説明する**。 + +| 概念 | 説明すべき内容 | +| --------------------------- | --------------------------------------------------------- | +| `&T` / `&mut T` | 所有権を移さず値を参照する仕組み。移動(move)との違い | +| `Result` | エラーを型で表現する仕組み。`try-catch`との違い | +| `Option` | 値があるかないかを型で表現する。`null`との違い | +| `?` 演算子 | `Result`/`Option`のエラーを呼び出し元に伝播させる糖衣構文 | +| ライフタイム `'a` | 参照の有効期間をコンパイラに伝えるアノテーション | +| トレイト境界 `T: Ord` | ジェネリクス型に「この機能が必要」と制約をつける仕組み | +| `impl Trait` vs `dyn Trait` | 静的ディスパッチと動的ディスパッチの違いと使い分け | +| イテレータアダプタ | `.map()` `.filter()` などがゼロコストになる理由 | + +#### 用語集ブロック(セクション末尾に追加) + +- 各セクションの末尾に「📖 このセクションで登場した用語」として、**そのセクションで使った専門用語の一覧と平易な説明**を付ける。 + +--- + +## 問題文 + +上記で回答した同じ問題 + +## 要件 + +### 1. 問題分析 + +- **競技プログラミング視点**: 実行速度・メモリ効率を最優先とした分析 +- **業務開発視点**: 型安全性・可読性・保守性・エラーハンドリングを重視した分析 +- **Rust特有の考慮点**: 所有権・借用・ライフタイム・ゼロコスト抽象化を考慮 + +### 2. アルゴリズム比較 + +複数のアルゴリズムアプローチを挙げ、各々の時間計算量・空間計算量をBig-O記法で示してください。Rust特有の所有権モデルと実装コストも考慮してください。 + +### 3. 実装方針 + +最適と考える方法を選択し、その理由を説明してください。Rustでの安全性と実行時性能の両立(Fearless Concurrency・ゼロコスト抽象化)を最優先に考慮してください。 + +### 4. コード実装 + +処理は関数として実装し、以下の点を意識してください: + +- **Pure function**: 副作用なし、同じ入力に対して同じ出力 +- **型安全性**: 厳密な型定義とジェネリクス・トレイト境界の活用 +- **エラーハンドリング**: `Result` / `Option` による型レベルのエラー表現 + +```rust +use std::collections::HashMap; // 必要な場合のみ + +/// 関数の説明 +/// +/// # Arguments +/// * `param` - パラメータの説明 +/// +/// # Returns +/// 戻り値の説明 +/// +/// # Errors +/// エラー条件 +/// +/// # Panics +/// パニックが起きる条件(起きないなら記述不要) +/// +/// # Complexity +/// - Time: O(n) +/// - Space: O(1) +fn algorithm_name(param: &[T]) -> Result +where + T: Ord + Copy, +{ + // 入力バリデーション(型レベル + 実行時) + if param.is_empty() { + return Err(AlgorithmError::EmptyInput); + } + + if param.len() > 1_000_000 { + return Err(AlgorithmError::InputTooLarge(param.len())); + } + + // 型安全なアルゴリズム実装 + // - 所有権・借用の明確な管理 + // - イミュータブルなデータ操作を優先 + // - unsafe ブロックは原則禁止 + todo!() +} + +// カスタムエラー型 +#[derive(Debug, Clone, PartialEq)] +enum AlgorithmError { + EmptyInput, + InputTooLarge(usize), + InvalidValue(String), +} + +impl std::fmt::Display for AlgorithmError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EmptyInput => write!(f, "Input slice is empty"), + Self::InputTooLarge(n) => write!(f, "Input size {n} exceeds limit"), + Self::InvalidValue(msg) => write!(f, "Invalid value: {msg}"), + } + } +} + +impl std::error::Error for AlgorithmError {} + +// 型エイリアス・ニュータイプ +type AlgorithmResult = Result; + +// ゼロコスト抽象化のためのトレイト定義(必要な場合) +trait Processable: Sized + Clone { + fn process(&self) -> AlgorithmResult; +} +``` + +### 5. 制約条件 + +- 外部クレート使用禁止(`std` 標準ライブラリのみ利用可) +- `unsafe` ブロックは原則禁止(使用する場合は必ず正当な理由を明示) +- Rust Edition 2021・`#![deny(clippy::all)]` 相当のコードクオリティ必須 + +--- + +## 回答形式 + +### 1. 問題の分析 + +> 💡 **初学者向け補足**:まずこの問題が「何を求めているのか」を一言で言い換えてから分析を始めること。 +> 例:「この問題は、一言で言うと『スライスの中から条件を満たす値を安全に探す問題』です。」 +> また、**Rustで解く際に特に気をつけるべき点**(所有権・借用・エラー処理の設計方針)を最初に1段落でまとめること。 + +- **競技プログラミング視点での分析** + - 実行速度を最優先とした場合のアプローチ + - メモリ使用量の最小化方針(スタック vs ヒープ・アロケーション回数) +- **業務開発視点での分析** + - 型安全性・保守性・可読性を重視した場合のアプローチ + - `Result` / `Option` によるエラーハンドリング設計 +- **Rust特有の考慮点** + - 所有権・借用・ライフタイムの設計方針 + - トレイト境界とモノモーフィゼーションによる最適化 + - イテレータアダプタ vs 命令型ループの選択基準 + +> 📖 **このセクションで登場した用語**(例) +> +> - **所有権**:値を"誰が管理するか"をコンパイル時に決めるRust独自の仕組み。メモリを自動で安全に管理できる +> - **借用**:所有権を渡さずに値を参照する仕組み。`&T`(読み取り専用)と`&mut T`(書き込み可能)がある +> - **スタック**:関数の呼び出しに使われる高速なメモリ領域。サイズが固定の値を置く +> - **ヒープ**:動的にサイズが変わる値(`Vec`など)を置く領域。スタックより低速だがサイズを自由に変えられる + +--- + +### 2. アルゴリズムアプローチ比較 + +> 💡 **初学者向け補足**:表の前に「なぜ複数のアプローチを比較するのか」を1〜2文で説明すること。 +> また、**Rust固有の観点**(所有権の移動が発生するか・アロケーションが必要か)についても各アプローチで触れること。 + +| アプローチ | 時間計算量 | 空間計算量 | Rust実装コスト | 安全性 | 可読性 | 備考 | +| ---------- | ---------- | ---------- | -------------- | ------ | ------ | ------------------------------ | +| 方法A | O(n) | O(1) | 低 | 高 | 高 | イテレータチェーン活用 | +| 方法B | O(n log n) | O(n) | 中 | 高 | 中 | `BTreeMap` / `BinaryHeap` | +| 方法C | O(n²) | O(1) | 低 | 高 | 高 | ネストループ(小規模入力向け) | + +> 💡 **Big-O記法の読み方**(初学者向け) +> +> - `O(1)`:入力の大きさに関わらず、常に一定の時間・メモリで済む(最速・最小) +> - `O(n)`:入力が2倍になると、処理も約2倍になる(線形) +> - `O(n log n)`:入力が2倍になると、処理は約2倍強になる(ソートアルゴリズムに多い) +> - `O(n²)`:入力が2倍になると、処理は約4倍になる(二重ループに多い) + +--- + +> 📖 **このセクションで登場した用語** +> +> - **時間計算量**:入力の大きさに対して、処理にかかる手間がどう増えるかの目安 +> - **空間計算量**:処理中に使うメモリ量がどう増えるかの目安 +> - **アロケーション**:ヒープ上にメモリを確保する操作。頻繁に行うと速度が落ちる +> - **イテレータチェーン**:`.map().filter().fold()`のように処理を数珠つなぎにする書き方 + +--- + +### 3. 選択したアルゴリズムと理由 + +> 💡 **初学者向け補足**:選んだ理由を「〜だから選ばなかった」という対比形式で説明すること。 +> また、**Rust固有の理由**(所有権モデルとの相性・アロケーション回数・ゼロコスト抽象化の活かしやすさ)を必ず含めること。 + +- **選択したアプローチ**: +- **理由**: + - 計算量的な優位性 + - Rustの所有権モデルとの親和性 + - 保守性・可読性の観点 +- **Rust特有の最適化ポイント**: + - ゼロコスト抽象化によるオーバーヘッドの排除 + - モノモーフィゼーションによるインライン展開 + - スタックアロケーション優先によるキャッシュ効率 + +> 📖 **このセクションで登場した用語** +> +> - **ゼロコスト抽象化**:便利な高レベルな書き方(イテレータなど)をしても、手書きの低レベルコードと同等の速さになるRustの特性 +> - **モノモーフィゼーション**:`fn f(x: T)`のようなジェネリクス関数が、使われる型ごとに専用コードへ自動展開される仕組み。動的ディスパッチより高速 +> - **キャッシュ効率**:CPUが直前に読んだメモリの近くにある値を素早く読める性質。スタック上の連続したデータは効率が良い + +--- + +### 4. 実装コード + +> 💡 **初学者向け補足**:コード全体を示す前に、「このコードの大まかな構造(骨格)」を箇条書きで示すこと。 +> 例: +> +> 1. カスタムエラー型を定義する(失敗の種類を型で表現するため) +> 2. 入力スライスを借用で受け取り、バリデーションを行う +> 3. イテレータアダプタでメインロジックを実装し `Result` で返す + +```rust +// ---- 型定義 ---- + +#[derive(Debug, Clone, PartialEq)] +enum AlgorithmError { + EmptyInput, + InputTooLarge(usize), + InvalidValue(String), +} + +impl std::fmt::Display for AlgorithmError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EmptyInput => write!(f, "Input slice is empty"), + Self::InputTooLarge(n) => write!(f, "Input size {n} exceeds limit"), + Self::InvalidValue(msg) => write!(f, "Invalid value: {msg}"), + } + } +} + +impl std::error::Error for AlgorithmError {} + +type AlgorithmResult = Result; + +// ---- メイン実装 ---- + +/// アルゴリズムの説明 +/// +/// # Arguments +/// * `input` - 入力スライス(借用・イミュータブル) +/// +/// # Returns +/// `Ok(値)` または `Err(AlgorithmError)` +/// +/// # Complexity +/// - Time: O(...) +/// - Space: O(...) +fn algorithm(input: &[T]) -> AlgorithmResult +where + T: Ord + Copy + std::fmt::Debug, +{ + // ---- 入力バリデーション ---- + if input.is_empty() { + return Err(AlgorithmError::EmptyInput); + } + + if input.len() > 1_000_000 { + return Err(AlgorithmError::InputTooLarge(input.len())); + } + + // ---- アルゴリズムロジック(イミュータブル優先) ---- + let result = process_input(input)?; + + Ok(result) +} + +// ---- ヘルパー関数 ---- + +/// 内部処理(型安全・副作用なし) +/// +/// # Complexity +/// - Time: O(n) +/// - Space: O(1) +fn process_input(input: &[T]) -> AlgorithmResult { + // 実装詳細 + // イテレータアダプタを積極活用(ゼロコスト抽象化) + input + .iter() + .copied() + .reduce(|acc, x| if x > acc { x } else { acc }) + .ok_or(AlgorithmError::EmptyInput) +} +``` + +> 💡 **コードの動作トレース**(初学者向け) +> 上記コードに対して、具体的な入力例(例:`&[3, 1, 4, 1, 5]`)を使い、 +> 各ステップで変数・イテレータの状態がどう変化するかをステップごとに示すこと。 +> +> ``` +> 例) +> 初期状態: input = &[3, 1, 4, 1, 5] +> Step 1: 入力検証 → is_empty()=false、len()=5 → バリデーション通過 +> Step 2: .iter() → 各要素への参照を順に取り出すイテレータを生成 +> Step 3: .copied() → &T(参照)を T(値)にコピー(Copy トレイトが必要な理由) +> Step 4: .reduce() → acc=3, x=1 → acc 維持 ...(以降を続ける) +> 結果: Ok(5) +> ``` + +--- + +> 💡 **`?` 演算子の動きを図で理解する**(初学者向け) +> `process_input(input)?` の `?` は、以下の処理と等価です: +> +> ``` +> Err が返ってきた場合 → そのまま呼び出し元に Err を返して関数を抜ける +> Ok が返ってきた場合 → Ok の中身を取り出して処理を続ける +> ``` +> +> これにより、エラーチェックのための `match` を毎回書く必要がなくなります。 + +--- + +> 📖 **このセクションで登場した用語** +> +> - **スライス `&[T]`**:配列やVecの一部(または全体)への参照。所有権を移さずにデータを渡せる +> - **`Copy` トレイト**:`i32` などの小さい値型が持つ性質。代入しても所有権が移らず値がコピーされる +> - **`?` 演算子**:`Result` や `Option` がエラー・Noneだったとき、自動で呼び出し元に返す糖衣構文 +> - **`reduce()`**:イテレータの要素を左から順に畳み込む操作。初期値なし版の`fold()` +> - **`ok_or()`**:`Option` を `Result` に変換するメソッド。`None` のときのエラー値を指定できる + +--- + +## Rust固有の最適化観点 + +### 所有権・借用・ライフタイムの活用 + +1. **所有権によるメモリ安全性** + - ガベージコレクタ不要のメモリ管理 + - コンパイル時のダングリングポインタ防止 + - `Clone` / `Copy` の使い分けによるアロケーション最小化 +2. **借用チェッカーの活用** + - `&T`(共有参照)と `&mut T`(排他参照)の明確な分離 + - ライフタイムアノテーション `'a` による参照の有効期間保証 + - 借用規則によるデータ競合のコンパイル時排除 +3. **スマートポインタの選択** + - `Box`: ヒープアロケーション・再帰型 + - `Rc` / `Arc`: 参照カウント(シングル / マルチスレッド) + - `Cell` / `RefCell`: 内部可変性パターン + +### ゼロコスト抽象化 + +1. **イテレータアダプタ** + - `.map()` / `.filter()` / `.fold()` は命令型ループと同等のアセンブリを生成 + - `.chain()` / `.zip()` / `.flat_map()` による合成 + - `.collect::>()` でのアロケーション制御 +2. **モノモーフィゼーション** + - ジェネリクス `` は型ごとにコード生成(動的ディスパッチなし) + - `impl Trait`(静的ディスパッチ)vs `dyn Trait`(動的ディスパッチ)の選択 + - インライン展開による関数呼び出しオーバーヘッドの排除 +3. **スタック優先設計** + - 固定サイズ型はスタックに配置(キャッシュフレンドリー) + - 配列 `[T; N]` vs `Vec` の使い分け + - `#[inline]` / `#[inline(always)]` アトリビュートの活用 + +### エラーハンドリング設計 + +1. **`Result` によるエラー伝播** + - `?` 演算子によるボイラープレートの排除 + - `From` / `Into` トレイトによるエラー型変換 + - カスタムエラー型と `std::error::Error` トレイトの実装 +2. **`Option` による null 安全性** + - `None` による欠損値の明示的表現 + - `.unwrap_or()` / `.unwrap_or_else()` / `.map_or()` の使い分け + - `if let` / `while let` / `?` による安全な展開 +3. **パニックの制御** + - `panic!` は回復不能なバグのみに限定 + - `.expect("reason")` による意図を明示したパニック + - `#[cfg(debug_assertions)]` を活用した開発時アサーション + +### 開発効率と保守性 + +- **コンパイラエラーメッセージによる設計支援** — Rustコンパイラは詳細な修正提案を提示 +- **`clippy` による静的解析** — `#![deny(clippy::all)]` でコードクオリティを保証 +- **`rustfmt` による自動整形** — チーム全体で一貫したスタイルを維持 +- **ドキュメントコメント `///`** — `cargo doc` で自動生成される型情報付きドキュメント +- **トレイトによる多態性** — インターフェース設計をコンパイル時に保証 + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 2.21 MB +// Beats 30.77% +use std::rc::Rc; +use std::cell::RefCell; + +impl Solution { + pub fn is_same_tree( + p: Option>>, + q: Option>>, + ) -> bool { + match (p, q) { + // ① 両方 None → 葉の先端に到達。構造が同じなので true + (None, None) => true, + + // ② 片方だけ Some → 構造が違う → false + // `|` で「どちらのパターンでも同じ処理」をまとめて表現 + (Some(_), None) | (None, Some(_)) => false, + + // ③ 両方 Some → 値を取り出して比較し、再帰で子木を確認 + (Some(p_node), Some(q_node)) => { + // .borrow() で RefCell の中身への共有参照を取り出す。 + // 実行時借用チェックが走るが、borrow_mut() を使わない + // 本実装ではパニックは発生しない。 + let p_ref = p_node.borrow(); + let q_ref = q_node.borrow(); + + // 値が違えば即 false(以降の再帰を省略できる) + if p_ref.val != q_ref.val { + return false; + } + + // 左の子木を再帰比較。 + // clone() は Rc の参照カウントを +1 するだけで O(1)。 + // ディープコピー(全ノード複製)は発生しない。 + let left_same = + Self::is_same_tree(p_ref.left.clone(), q_ref.left.clone()); + + // 左が不一致なら右を見るまでもなく false + if !left_same { + return false; + } + + // 右の子木を再帰比較し、その結果をそのまま返す + Self::is_same_tree(p_ref.right.clone(), q_ref.right.clone()) + } + } + } +} +``` diff --git a/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_TypeScript.md b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_TypeScript.md new file mode 100644 index 0000000..9c27bd9 --- /dev/null +++ b/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/Same_Tree_TypeScript.md @@ -0,0 +1,176 @@ +## 1. 問題の分析 + +> 💡 **初学者向け補足**:この問題を一言で言うと、「2本の木(ツリー)が、形も値もまったく同じかどうかを確認する問題」です。木を上から下に向かってたどりながら、対応するノード(節点)が一致しているかをすべて確認します。 + +--- + +**競技プログラミング視点での分析** + +全ノードを最低1回は訪問しないと「同じかどうか」を確定できません。したがって最低でも O(n) の処理が必要で、これが理論上の下限です。メモリは再帰の深さ(木の高さ h)ぶんのスタックを消費します。最悪ケース(片方に偏った木)は O(n)、平均ケースは O(log n) です。 + +**業務開発視点での分析** + +`null` チェックが多発する構造なので、型システムで `TreeNode | null` を明示し、コンパイル時に未処理の `null` を排除することが重要です。再帰関数にすることで「同じ処理を左右の子に繰り返す」という意図が読み手に伝わりやすくなります。 + +**TypeScript特有の考慮点** + +LeetCode の定義済みクラス `TreeNode` をそのまま使います。戻り値型 `boolean` を明示することで、誤って数値や文字列を返すミスをコンパイル時に防げます。`null` との比較は `===` を使い、型ガードを活用します。 + +> 📖 **このセクションで登場した用語** +> +> - **ノード(節点)**:木構造の各要素。値と、子への参照を持つ +> - **型ガード**:「この値が特定の型かどうか」を実行時に確認し、TypeScriptに型を教える処理 +> - **コンパイル時**:TypeScriptのコードをJavaScriptに変換する段階。ここでエラーを検出できると実行時のバグを防げる + +--- + +## 2. アルゴリズムアプローチ比較 + +> 💡 **初学者向け補足**:同じ問題でも解き方は複数あります。それぞれの「速さ(時間計算量)」と「メモリの使い具合(空間計算量)」を比べて最適なものを選びます。 + +| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +| --------------------------- | ---------- | ---------- | ------------ | -------- | ------ | ------------------------ | +| **再帰DFS**(深さ優先) | O(n) | O(h) | 低 | 高 | 高 | 問題構造と直接対応 | +| **反復BFS**(幅優先)キュー | O(n) | O(n) | 中 | 高 | 中 | キューの実装が必要 | +| **反復DFS** スタック | O(n) | O(h) | 中 | 高 | 中 | 明示的スタック管理が必要 | + +> 💡 **Big-O記法の読み方**(初学者向け) +> +> - `O(n)`:ノード数 n に比例した時間・メモリが必要(線形) +> - `O(h)`:木の高さ h に比例したメモリが必要。バランスの良い木なら `O(log n)`、最悪ケースで `O(n)` + +--- + +> 📖 **このセクションで登場した用語** +> +> - **DFS(深さ優先探索)**:木の根から葉まで深く潜ってから戻る探索法。「縦に掘り進む」イメージ +> - **BFS(幅優先探索)**:同じ深さのノードを横方向にすべて調べてから次の深さへ進む探索法 +> - **スタック**:「最後に積んだものを最初に取り出す」構造(本の山積みをイメージ) + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択したアプローチ**:再帰 DFS(深さ優先探索) + +- **理由**: + - BFS を選ばなかった理由:キューを配列で管理すると O(n) の追加メモリが常に必要。再帰DFSは木の高さぶんのスタックしか使わない + - 反復DFSを選ばなかった理由:スタックを明示的に管理するコードが増え、読みやすさが下がる + - 再帰DFSが優れる理由:「2つの木が同じ ⟺ 根の値が同じ かつ 左の部分木が同じ かつ 右の部分木が同じ」という問題の定義そのものが再帰になっており、コードと定義が1対1で対応している + +- **TypeScript特有の最適化ポイント**: + - 引数型 `TreeNode | null` を明示し、`null` を渡してもクラッシュしない + - 戻り値型 `boolean` を明示し、うっかり `undefined` を返すミスをコンパイル時に防ぐ + - 早期リターン(early return)パターンで `null` ケースを先に処理することで、以降のコードで `null` を心配せずに `.val` などにアクセスできる + +> 📖 **このセクションで登場した用語** +> +> - **早期リターン(early return)**:関数の先頭で例外ケースを先に返すことで、以降のコードをシンプルに保つテクニック +> - **部分木(サブツリー)**:ある木の中の特定ノードを根として切り出した、より小さな木 + +--- + +ここで図解を確認しましょう。再帰 DFS がどのように木を比較するかを視覚的に示します。 + +まず、2本の木の構造と、再帰がどの順序でノードを訪問するかを示します。次に、「同じでない」ケース(Example 2)で再帰がどの時点で `false` を返すかを示します。--- + +## 4. 実装コード + +> 💡 **初学者向け補足**:このコードの骨格は以下の通りです。 +> +> 1. 両方が `null` なら → 同じ(`true`) +> 2. 片方だけ `null` なら → 違う(`false`) +> 3. 両方に値があり、値が違うなら → 違う(`false`) +> 4. 値が同じなら → 左の子木・右の子木を再帰的に比較 + +```typescript +// Runtime 0 ms +// Beats 100.00% +// Memory 55.69 MB +// Beats 56.57% + +/** + * 2つの二分木が同一かどうかを再帰DFSで判定する + * @param p - 比較元の木のノード(または null) + * @param q - 比較先の木のノード(または null) + * @returns 2つの木が構造・値ともに完全に一致する場合 true + * @complexity Time: O(n), Space: O(h) + * n = 総ノード数, h = 木の高さ(再帰スタックの深さ) + */ +function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean { + // ── ① 両方 null のとき ────────────────────────────── + // 両方が null ということは「どちらにも子が存在しない」 + // = 構造が同じ(null == null は true)なので true を返す + // ※ 葉ノードのさらに下は必ずここに到達する + if (p === null && q === null) return true; + + // ── ② 片方だけ null のとき ────────────────────────── + // p だけ null、または q だけ null → 構造が違う → false + // ここで両方 null のケースはすでに上で return 済みなので、 + // どちらか一方だけ null の場合のみこの条件に入る + if (p === null || q === null) return false; + + // ── ③ どちらも null でない → 値を比較 ────────────── + // ここに到達した時点で p も q も非 null であることが確定している。 + // TypeScript の型システムも、ここでは p・q を TreeNode として扱う。 + // 値が違うなら木の内容が異なる → false + if (p.val !== q.val) return false; + + // ── ④ 値が一致 → 左右の子木を再帰で比較 ──────────── + // 「p と q が同じ木」 ⟺ + // 「根の値が同じ」かつ「左の子木が同じ」かつ「右の子木が同じ」 + // この定義をそのままコードにしたのが以下の1行。 + // && (AND) を使うことで、左が false なら右の再帰は実行されない + // (短絡評価=ムダな比較をしない) + return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); +} +``` + +--- + +> 💡 **コードの動作トレース**(Example 2: `p=[1,2]`, `q=[1,null,2]`) +> +> ``` +> 呼び出し①: isSameTree(p=Node(1), q=Node(1)) +> → ①③ を通過(両方非 null, 1 == 1) +> → isSameTree(p.left, q.left) を呼び出す +> +> 呼び出し②: isSameTree(p=Node(2), q=null) +> → p は非 null, q は null +> → ② の条件: p === null || q === null → true +> → 即座に false を返す ← ここで終了! +> +> ② が false を返した瞬間、① の && 演算子の左辺が false になる。 +> 短絡評価により右辺(p.right vs q.right)は実行されない。 +> 全体の結果: false +> ``` + +--- + +> 📖 **このセクションで登場した用語** +> +> - **短絡評価(ショートサーキット)**:`A && B` の A が `false` の時点で B を評価せず `false` を返す仕組み。無駄な処理を省ける +> - **再帰(recursion)**:関数が自分自身を呼び出す仕組み。「木の比較 = 根の比較 + 左の子木の比較 + 右の子木の比較」という繰り返し構造に最適 +> - **O(h)**:h は木の高さ(height)。平衡二分木(バランスが取れた木)では `h ≈ log₂ n`、最悪(一本道)では `h = n` + +--- + +## TypeScript固有の最適化観点 + +### 型安全性の活用 + +`TreeNode | null` という**ユニオン型**(=「AかBのどちらかの型」を表す TypeScript の仕組み)を引数に使うことで、「ノードか null を渡す」という仕様を型が文書として機能します。①②のチェック後、③④では TypeScript が「ここでは p・q は必ず `TreeNode` だ」と型を絞り込んでくれます(これを**型の絞り込み / narrowing** と言います)。 + +### コンパイル時最適化 + +`strict: true` モード下では、`null` チェックなしに `p.val` へアクセスしようとするとコンパイルエラーになります。今回のコード構成は「先に `null` を排除してから `.val` を触る」という構造になっているため、**コンパイラーが安全性を保証したうえで実行される**、理想的な形です。 + +### 開発効率と保守性 + +戻り値 `boolean` を明示したことで、IDE の IntelliSense が `isSameTree(...)` の呼び出し元に型情報を提供します。将来このロジックを別の関数に組み込む際も、型の不一致をコンパイラーが即座に検出してくれます。 + +> 📖 **最終用語まとめ** +> +> - **ユニオン型**:`A | B` の形で「A か B のどちらか」の型を表す TypeScript の機能 +> - **型の絞り込み(narrowing)**:`if (p === null)` などのチェックの後で TypeScript が自動的に型を絞り込む仕組み +> - **IntelliSense**:IDE が型情報をもとに補完候補やエラーをリアルタイムに表示する機能。型定義が詳細なほど精度が上がる diff --git a/public/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/README_react.html b/public/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/README_react.html new file mode 100644 index 0000000..88c396a --- /dev/null +++ b/public/Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/README_react.html @@ -0,0 +1,1997 @@ + + + + + + LeetCode 100 — Same Tree | 再帰DFS解説 + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    +

    + アルゴリズム概要 +

    + +
    +

    + 💡 + この問題を一言で言うと:「2本の二分木が、形も値もまったく同じかどうかを確認する問題」 +

    +

    + 二分木(=各ノードが左と右に高々1つずつ子を持つ木構造)が2本与えられます。 + 「同じ木」とは、すべての対応するノードが同じ値を持ち、かつ木の形(どこに子がいるか)も完全に一致することです。 + 単純に見えますが、「木の形の比較」という点で少し考える必要があります。 +

    +
    + +
    +

    + ⚠️ なぜ単純な方法では解けないのか +

    +
      +
    • + null(何もない)の扱いが難しい:片方の木にはノードがあり、もう片方には何もない(null)という場合を正確に区別しなければならない +
    • +
    • + 全ノードを調べる必要がある:根(ルート)の値が同じでも、葉(末端)の値や位置が違えば「異なる木」になる。部分的な確認では不十分 +
    • +
    +
    + +
    +
    +
    O(n)
    +
    時間計算量
    +
    +
    +
    O(h)
    +
    空間計算量
    +
    +
    +
    + 再帰DFS +
    +
    アルゴリズム
    +
    +
    +
    Easy
    +
    難易度
    +
    +
    + + +
    +
    +

    + 例1 → true +

    +
    +p: [1,2,3]    q: [1,2,3]
    +    1              1
    +   / \            / \
    +  2   3          2   3
    +

    形も値もすべて同じ → true ✅

    +
    +
    +

    + 例2 → false +

    +
    +p: [1,2]      q: [1,null,2]
    +    1              1
    +   /                \
    +  2                  2
    +

    + 値は同じでも位置(左 vs 右)が違う → false ❌ +

    +
    +
    +

    + 例3 → false +

    +
    +p: [1,2,1]    q: [1,1,2]
    +    1              1
    +   / \            / \
    +  2   1          1   2
    +

    + 左右の値が入れ替わっている → false ❌ +

    +
    +
    + +
    +

    📌 制約

    +
      +
    • + 両方の木のノード数は + 0 以上 + 100 以下 +
    • +
    • + -10⁴ ≤ Node.val ≤ 10⁴ +
    • +
    +
    +
    + + +
    +

    + ステップバイステップ解説 +

    +

    + 再帰DFSがどのように動くかを4つのステップで確認しましょう。 ▶ Play + ボタンで自動的に進めることもできます。 +

    +
    +
    + + +
    +

    + Python 実装 +

    + +
    +

    + 📋 このコードの構造(先に全体像を把握しよう) +

    +
      +
    1. + 両方が + None + かを確認 → 同じ葉の先端なら + True +
    2. +
    3. + 片方だけ + None + かを確認 → 構造が違うので + False +
    4. +
    5. + 両方の値(p.val != q.val)が違うなら + False +
    6. +
    7. + 左の子木・右の子木を再帰で比較し、両方一致なら + True +
    8. +
    +
    + +
    class Solution(object):
    +    def isSameTree(self, p, q):
    +        """
    +        :type p: Optional[TreeNode]
    +        :type q: Optional[TreeNode]
    +        :rtype: bool
    +        """
    +        # ── ① 両方 None のとき ──────────────────────────
    +        # 葉ノードのさらに下(何もない場所)に両方とも到達した。
    +        # 「どちらにも子がない」=構造が一致している → True
    +        # `is None` を使うのが Pythonic(Pythonらしい慣用的な書き方)
    +        if p is None and q is None:
    +            return True
    +
    +        # ── ② 片方だけ None のとき ──────────────────────
    +        # ①で「両方 None」はすでに return 済み。
    +        # ここに来るのは「どちらか一方だけ None」の場合のみ。
    +        # 片方にノードがあり、片方にない = 構造が違う → False
    +        if p is None or q is None:
    +            return False
    +
    +        # ── ③ 値の比較 ───────────────────────────────────
    +        # ①②を通過した時点で p も q も None でないことが確定。
    +        # pylance もここでは p・q を TreeNode として認識する
    +        # (型の絞り込み = Type Narrowing と呼ばれる仕組み)。
    +        if p.val != q.val:
    +            return False
    +
    +        # ── ④ 左右の子木を再帰で比較 ────────────────────
    +        # 根の値が一致したので、次は左・右の子木を同じ手順で比較する。
    +        # `and` の短絡評価(左が False なら右は実行しない)で
    +        # 不一致が見つかった時点で即座に False を返せる。
    +        return (
    +            self.isSameTree(p.left, q.left)
    +            and self.isSameTree(p.right, q.right)
    +        )
    + +
    +

    + ▶ 入力例 p=[1,2] / q=[1,null,2] での動作トレース +

    +
    +呼び出し①: isSameTree(Node(1), Node(1))
    +  → ① 両方非 None → パス
    +  → ② どちらも非 None → パス
    +  → ③ 1 == 1 → パス(値が等しいので続ける)
    +  → ④ 左の子を比較するために再帰呼び出し
    +
    +呼び出し②: isSameTree(Node(2), None)   ← p.left=Node(2), q.left=None
    +  → ① p は非 None → パス(両方 None ではない)
    +  → ② p は非 None だが q は None → return False ← ここで終了!
    +
    +呼び出し①に戻る:
    +  → isSameTree(p.left, q.left) = False
    +  → and の短絡評価:False and ... → 右辺の再帰は実行されない
    +  → return False
    +
    +最終結果: False ✅
    +
    +
    + + +
    +

    + 処理フローチャート +

    + +
    +

    + 🗺️ フローチャートの読み方 +

    +
    +
    + + + + 楕円(緑)= 開始・終了 +
    +
    + + + + 四角(青)= 処理ステップ +
    +
    + + + + ひし形(黄)= 条件分岐 +
    +
    + 緑=はい + 赤=いいえ +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + 開始: isSameTree(p, q) + + + + + + + + + p と q は + + + どちらも None? + + + + + + はい + + + + + + True + + + + + + いいえ + + + + + + どちらか一方だけ + + + None? + + + + + + はい + + + + False + + + + + + いいえ + + + + + + p.val ≠ q.val + + + (値が違う)? + + + + + + はい + + + + False + + + + + + いいえ + + + + + + isSameTree(p.left, q.left) を再帰呼び出し + + + + + + + + + 左の結果は + + + True? + + + + + + いいえ + + + + False + + + + + + はい + + + + + + isSameTree(p.right, q.right) を再帰呼び出し + + + + + + + + + 右の結果は + + + True? + + + + + + いいえ + + + + False + + + + + + はい + + + + + + 終了: True を返す + + + + + + + + 再帰ループ(子ノードへ) + + +
    + +
    +

    + 🔎 入力例 p=[1,2,3] / q=[1,2,3] でのフロー追跡 +

    +
      +
    1. 「開始」ノード → isSameTree(Node(1), Node(1)) を受け取る
    2. +
    3. + 「どちらも None?」ノード → 両方非 None → + いいえ の経路へ +
    4. +
    5. + 「片方だけ None?」ノード → どちらも非 None → + いいえ の経路へ +
    6. +
    7. + 「p.val ≠ q.val?」ノード → 1 == 1 → + いいえ の経路へ(値が等しいので続ける) +
    8. +
    9. + 「左の子木を再帰比較」→ isSameTree(Node(2), Node(2)) + を再帰呼び出し(さらに深く潜る) +
    10. +
    11. 「左の結果 True?」→ 左の子木も一致 → はい の経路へ
    12. +
    13. 「右の子木を再帰比較」→ isSameTree(Node(3), Node(3)) を再帰呼び出し
    14. +
    15. 「右の結果 True?」→ 右の子木も一致 → はい の経路へ
    16. +
    17. 「終了」ノード → True を返す ✅
    18. +
    +
    +
    + + +
    +

    + 計算量分析 +

    + +
    +

    + 📖 Big-O + 記法の読み方(入力サイズが大きくなるにつれて処理時間がどう増えるかの目安) +

    +
    +
    +
    O(1)
    +
    + 常に一定
    例:辞書の直接引き +
    +
    +
    +
    O(n)
    +
    + 入力に比例
    例:リストを1回走査 +
    +
    +
    +
    O(n log n)
    +
    + n より少し多い
    例:ソートアルゴリズム +
    +
    +
    +
    O(n²)
    +
    + 入力の2乗
    例:二重ループ総当たり +
    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    観点計算量条件
    時間計算量O(n) + n = 2つの木の総ノード数。全ノードを最大1回訪問 +
    空間計算量(平均)O(log n) + 平衡二分木の場合(高さ h ≈ log₂ n) +
    空間計算量(最悪)O(n) + 一本道の木(高さ = ノード数)の場合 +
    本問題での実際O(100) + ノード数 ≤ 100 の制約により事実上定数 +
    +
    + +
    +

    + 🔍 なぜこの計算量になるのか +

    +

    + 時間計算量 O(n):「2つの木が同じかどうか」を確認するには、すべてのノードを少なくとも1回は調べなければなりません。再帰DFSは各ノードをちょうど1回だけ訪問するため、n + ノードに対して O(n) の操作で済みます。
    + 空間計算量 O(h):再帰呼び出しはコールスタック(=関数呼び出しの積み重ね)にメモリを使います。一番深くまで潜ったとき(葉ノードに到達したとき)の積み重ねの深さが木の高さ + h なので、O(h) + のメモリが必要です。追加のデータ構造(リストやキューなど)は一切使わないため、スタック以外のメモリは + O(1) です。 +

    +
    + + +
    +

    📊 アプローチ別比較

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    アプローチ時間空間特徴
    + ✅ 再帰DFS(採用) + O(n)O(h) + コードが最もシンプル。問題の定義と1対1対応 +
    + 反復DFS(スタック) + O(n)O(h) + list をスタック代わりに使用。再帰を使わない +
    + 反復BFS(キュー) + O(n) + O(n) + + deque 使用。常に O(n) メモリを消費して不利 +
    +
    +
    +
    + + +
    +

    + 📖 用語集 +

    +

    + このページで登場した専門用語をまとめました。分からない言葉が出てきたときに参照してください。 +

    +
    +
    + + None(ナン) + +
    + Pythonで「何もない」を表す特別な値。他の言語の + null + に相当します。 二分木では、子がいないノードの + left や + right + が + None + になります。 比較する際は + == None + ではなく + is None + を使うのが Pythonic です。 +
    +
    + +
    + + DFS(深さ優先探索 / + Depth-First Search) + +
    + 木やグラフを「根から葉まで深く潜ってから戻る」順番で探索する手法。 + 迷路を解くとき「行き止まりに当たるまでまっすぐ進み、行き止まりになったら戻って別の道を試す」のと同じ考え方です。 + 今回の問題では再帰関数が自動的に DFS の順序でノードを訪問します。 +
    +
    + +
    + + コールスタック(Call + Stack) + +
    + 関数を呼び出すたびに「呼び出し情報」を積み上げていくメモリ領域。 + お皿の山積みに例えると、新しい関数呼び出しのたびにお皿を1枚重ね、関数が終了するとお皿を1枚取り除きます。 + 再帰が深くなるほどお皿が積み重なり、メモリを消費します。これが空間計算量 + O(h) の理由です。 +
    +
    + +
    + + 再帰(Recursion) + +
    + 関数が自分自身を呼び出す仕組み。「木の比較」のように「同じ問題が小さいサイズで繰り返される」構造に特に適しています。 + 必ず「基底条件(これ以上深く行かない条件)」を設定しないと無限ループになるので注意が必要です。 + 今回の基底条件は + p is None and q is None + のときに + True + を返す部分です。 +
    +
    + +
    + + + 短絡評価(Short-Circuit Evaluation) + +
    + A and B + の A が + False + なら B を評価しない・ + A or B + の A が + True + なら B を評価しない仕組み。 今回のコードでは + isSameTree(p.left, q.left) and isSameTree(p.right, q.right) + において、 + 左の子木が一致しなければ右の再帰は実行されません。不必要な処理を省いて効率化できます。 +
    +
    + +
    + + 二分木(Binary + Tree) + +
    + 各ノード(節点)が高々2つの子(左の子・右の子)を持つ木構造のこと。 + 家系図に例えると、親が最大2人の子を持てる構造です。 今回の問題の + TreeNode + クラスはこの構造を + left と + right + の2つの参照で表現しています。 +
    +
    + +
    + + 平衡二分木(Balanced + Binary Tree) + +
    + 左右の子木の高さの差が小さい、バランスの取れた二分木のこと。 n + 個のノードを持つ平衡二分木の高さは約 log₂ n になります。 例えば 1000 + ノードなら高さは約 10 です。 逆に「一本道」の木(チェーン状)は高さが n + になり、最悪ケースの空間計算量 O(n) に相当します。 +
    +
    + +
    + + + Pythonic(パイソニック) + +
    + Pythonらしい、慣用的な書き方のこと。Pythonコミュニティが「この書き方が自然で読みやすい」と考えるスタイルを指します。 + 例えば + x == None + より + x is None、 + len(lst) == 0 + より + not lst + が Pythonic とされています。 +
    +
    +
    +
    + + +
    +

    LeetCode 100 — Same Tree | 再帰DFS による O(n) 実装解説

    +

    Python 3 · 初学者向け解説ページ

    +
    +
    + + + + + + + + diff --git a/public/index.html b/public/index.html index 790eaa9..bf6bb1a 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

    🧪 Algorithm Study Index

    -

    166 interactive lessons across 6 domains

    +

    167 interactive lessons across 6 domains

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

    - + @@ -466,6 +466,7 @@

  • 🧩Jump Game Algorithm AnalysisAlgorithm/greedy algorithm/leetcode/55. Jump Game/Claude/README.html
  • 🧩Jump Game II アルゴリズム解析Algorithm/greedy algorithm/leetcode/45. Jump Game II/Claude/README.html
  • 🧩LeetCode #83 - Remove Duplicates from Sorted ListAlgorithm/Other/leetcode/83. Remove Duplicates from Sorted List/Claude 4.6 extended/README_React.html
  • +
  • 🧩LeetCode 100 — Same Tree | 再帰DFS解説Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/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
  • @@ -639,6 +640,7 @@

  • 🧩Jump Game Algorithm AnalysisAlgorithm/greedy algorithm/leetcode/55. Jump Game/Claude/README.html
  • 🧩Jump Game II アルゴリズム解析Algorithm/greedy algorithm/leetcode/45. Jump Game II/Claude/README.html
  • 🧩LeetCode #83 - Remove Duplicates from Sorted ListAlgorithm/Other/leetcode/83. Remove Duplicates from Sorted List/Claude 4.6 extended/README_React.html
  • +
  • 🧩LeetCode 100 — Same Tree | 再帰DFS解説Algorithm/Other/leetcode/100. Same Tree/claude sonnet 4.6 extended/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
  • @@ -815,7 +817,7 @@

    🧪 - Generated on 2026-03-18 + Generated on 2026-03-19