|
| 1 | +# Binary Tree Inorder Traversal — Rust Edition |
| 2 | + |
| 3 | +## 1. 問題の分析 |
| 4 | + |
| 5 | +### 競技プログラミング視点での分析 |
| 6 | + |
| 7 | +- 全ノードを一度だけ訪れる **O(N) 時間・O(N) 空間**が理論下界 |
| 8 | +- LeetCode の Rust 環境では `TreeNode` が `Option<Rc<RefCell<TreeNode>>>` でラップされており、**所有権の移動なしに借用で読み取る** 設計が必須 |
| 9 | +- 明示スタックによる反復実装でコールスタック消費を排除 |
| 10 | + |
| 11 | +### 業務開発視点での分析 |
| 12 | + |
| 13 | +- `Option<Rc<RefCell<TreeNode>>>` という複合型を安全に扱うため、`.borrow()` による共有参照と `Option` の `?`/`if let` による null 安全な展開が鍵 |
| 14 | +- `Vec<i32>` への追記は `push` のみで副作用をローカルに限定 → Pure function に近い設計 |
| 15 | +- `Result` は不要(入力が空 = 空ベクタを返すだけで、エラー状態がない) |
| 16 | + |
| 17 | +### Rust特有の考慮点 |
| 18 | + |
| 19 | +- `Rc<RefCell<T>>` の共有所有権モデル:`clone()` はポインタのコピーのみ(O(1)) |
| 20 | +- `.borrow()` で `Ref<TreeNode>` を取得 → 借用スコープを最小化してデッドロック回避 |
| 21 | +- スタックの型を `Rc<RefCell<TreeNode>>` とすることで、非 null 要素のみを格納できる |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## 2. アルゴリズムアプローチ比較 |
| 26 | + |
| 27 | +| アプローチ | 時間計算量 | 空間計算量 | Rust実装コスト | 安全性 | 可読性 | 備考 | |
| 28 | +| --------------------------- | ---------- | ---------- | -------------- | ------ | ------ | --------------------------------------------------- | |
| 29 | +| **A: 再帰 DFS** | O(N) | O(N) | 低 | 高 | 最高 | コールスタック深さN、深い木でスタックオーバーフロー | |
| 30 | +| **B: 反復(明示スタック)** | O(N) | O(N) | 中 | 高 | 高 | Follow-up要件を満たす。`Rc::clone`でO(1)コピー | |
| 31 | +| **C: Morris Traversal** | O(N) | O(1) | 非常に高 | 低 | 低 | `RefCell`の可変借用が複数箇所で必要、実装困難 | |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## 3. 選択したアルゴリズムと理由 |
| 36 | + |
| 37 | +- **選択したアプローチ**: **B: 反復(明示スタック)** |
| 38 | +- **理由**: |
| 39 | + - Follow-upの反復解要件を満たす |
| 40 | + - `Rc<RefCell<T>>` モデルで Morris は `borrow_mut()` の多重借用を誘発しやすく危険 |
| 41 | + - `Rc::clone()` はポインタカウントのインクリメントのみで、ノード値コピーなし |
| 42 | + - スタック `Vec<Rc<RefCell<TreeNode>>>` で型安全かつ非 null 要素のみ管理 |
| 43 | + |
| 44 | +- **Rust特有の最適化ポイント**: |
| 45 | + - `Rc::clone(&node)` の明示的クローンでコスト意識を表現 |
| 46 | + - `.borrow()` の借用スコープを `{}` ブロックで最小化(借用の早期解放) |
| 47 | + - `Vec::with_capacity` でリアロケーション回数を削減(N≤100 なので省略可) |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 4. 実装コード |
| 52 | + |
| 53 | +```rust |
| 54 | +// Runtime 0 ms |
| 55 | +// Beats 100.00% |
| 56 | +// Memory 2.19MB |
| 57 | +// Beats 74.02% |
| 58 | + |
| 59 | +// leetcode環境では use std::rc::Rc; use std::cell::RefCell; が提供済み |
| 60 | + |
| 61 | +// ---- メイン実装 ---- |
| 62 | + |
| 63 | +/// Binary Tree Inorder Traversal(反復・明示スタック実装) |
| 64 | +/// |
| 65 | +/// アルゴリズム: |
| 66 | +/// 1. current カーソルを根から開始 |
| 67 | +/// 2. current が Some の間、左端までスタックに積む(Rc::clone でポインタコピー) |
| 68 | +/// 3. スタックから pop → 値を記録 → current を右子に移す |
| 69 | +/// 4. current と stack が両方空になったら終了 |
| 70 | +/// |
| 71 | +/// # Arguments |
| 72 | +/// * `root` - 二分木の根ノード(`None` = 空木) |
| 73 | +/// |
| 74 | +/// # Returns |
| 75 | +/// 中順走査の値ベクタ(空木の場合は空ベクタ) |
| 76 | +/// |
| 77 | +/// # Complexity |
| 78 | +/// - Time: O(N) — 全ノードを一度だけ訪問 |
| 79 | +/// - Space: O(N) — 明示スタック最大深さ N(最悪: 左に偏った木) |
| 80 | +pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> { |
| 81 | + // 結果バッファ(N ≤ 100 なのでデフォルト容量で十分) |
| 82 | + let mut result: Vec<i32> = Vec::new(); |
| 83 | + |
| 84 | + // 明示スタック:非 null ノードのみ格納(型レベルで None 混入を排除) |
| 85 | + let mut stack: Vec<Rc<RefCell<TreeNode>>> = Vec::new(); |
| 86 | + |
| 87 | + // カーソル:Option で「未訪問ノードあり」「なし」を型安全に表現 |
| 88 | + let mut current: Option<Rc<RefCell<TreeNode>>> = root; |
| 89 | + |
| 90 | + // current(未訪問)とstack(保留)のいずれかが残る間ループ |
| 91 | + while current.is_some() || !stack.is_empty() { |
| 92 | + |
| 93 | + // ---- フェーズ1: 左端まで潜りながらスタックに積む ---- |
| 94 | + while let Some(node_rc) = current { |
| 95 | + // Rc::clone はポインタカウントのインクリメントのみ(O(1)、コピーコストなし) |
| 96 | + stack.push(Rc::clone(&node_rc)); |
| 97 | + |
| 98 | + // .borrow() スコープを最小化: 左子のクローンを取得したら即解放 |
| 99 | + let left = node_rc.borrow().left.clone(); |
| 100 | + current = left; // 左へ進む(None なら次のwhileを脱出) |
| 101 | + } |
| 102 | + |
| 103 | + // ---- フェーズ2: スタック top を取り出して訪問 ---- |
| 104 | + // stack.is_empty() でないことはループ条件で保証済み → unwrap 安全 |
| 105 | + if let Some(node_rc) = stack.pop() { |
| 106 | + // 借用スコープを {} で明示的に限定(右子取得前に解放) |
| 107 | + let (val, right) = { |
| 108 | + let node = node_rc.borrow(); // Ref<TreeNode> |
| 109 | + (node.val, node.right.clone()) |
| 110 | + }; // ← ここで Ref が drop され、借用解放 |
| 111 | + |
| 112 | + // 中順で値を記録 |
| 113 | + result.push(val); |
| 114 | + |
| 115 | + // ---- フェーズ3: 右部分木へカーソルを移す ---- |
| 116 | + current = right; // None なら次ループで即 pop フェーズへ |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + result |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +## 5. アルゴリズム動作トレース |
| 127 | + |
| 128 | +`root = [1, null, 2, 3]` を例に各フェーズを可視化します。 |
| 129 | + |
| 130 | +``` |
| 131 | +ツリー構造: |
| 132 | + 1 |
| 133 | + \ |
| 134 | + 2 |
| 135 | + / |
| 136 | + 3 |
| 137 | +``` |
| 138 | + |
| 139 | +| ステップ | current | stack(底→top) | result | 操作 | |
| 140 | +| -------- | ------------ | --------------- | ------- | --------------------------------- | |
| 141 | +| 初期 | Some(1) | [] | [] | — | |
| 142 | +| Ph1 | None | [1] | [] | 1 を push、left=None で停止 | |
| 143 | +| Ph2 | — | [] | [1] | pop→1、val=1 を記録 | |
| 144 | +| Ph3 | Some(2) | [] | [1] | current = right(2) | |
| 145 | +| Ph1 | Some(3)→None | [2,3] | [1] | 2 push → 3 push、left=None で停止 | |
| 146 | +| Ph2 | — | [2] | [1,3] | pop→3、val=3 を記録 | |
| 147 | +| Ph3 | None | [2] | [1,3] | current = right(None) | |
| 148 | +| Ph2 | — | [] | [1,3,2] | pop→2、val=2 を記録 | |
| 149 | +| Ph3 | None | [] | [1,3,2] | ループ終了 | |
| 150 | + |
| 151 | +**Output: `[1, 3, 2]` ✅** |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Rust固有の最適化観点まとめ |
| 156 | + |
| 157 | +| 観点 | 本実装での適用 | |
| 158 | +| ----------------- | ---------------------------------------------------------------------- | |
| 159 | +| **所有権管理** | `Rc::clone` でポインタ共有(ノード値コピーなし) | |
| 160 | +| **借用の最小化** | `{ let node = node_rc.borrow(); ... }` で借用スコープを即解放 | |
| 161 | +| **null 安全性** | `Option<Rc<RefCell<TreeNode>>>` + `while let` で None を型レベルで排除 | |
| 162 | +| **Pure function** | 入力ツリーへの書き込みゼロ(`.borrow()` のみ、`.borrow_mut()` 不使用) | |
| 163 | +| **パニック制御** | `unwrap()` を排除し `if let` / `while let` で安全展開 | |
0 commit comments