From 2d504a542eae287bfd07a3c8d06a446a178e35ea Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 11 Apr 2026 02:48:23 +0900 Subject: [PATCH 1/4] feat: add TypeScript, Rust, and Python solutions for LeetCode 102 Binary Tree Level Order Traversal --- .gitignore | 2 + ...inary_Tree_Level_Order_Traversal_Python.md | 0 .../Binary_Tree_Level_Order_Traversal_Rust.md | 352 ++++++++++++++++++ ...y_Tree_Level_Order_Traversal_Typescript.md | 292 +++++++++++++++ public/index.html | 2 +- 5 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Python.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Rust.md create mode 100644 Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Typescript.md diff --git a/.gitignore b/.gitignore index 214f93db..cde77481 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ credentials.json # Node.js関連 node_modules/ package-lock.json + +.claude/skills diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Python.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Python.md new file mode 100644 index 00000000..e69de29b diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Rust.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Rust.md new file mode 100644 index 00000000..191d4eff --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Rust.md @@ -0,0 +1,352 @@ +> 🎯 **[algo-beginner スキル発火]** +> 言語/カテゴリ: Rust +> 適用ルールセット: 共通5ルール + Rust固有5ルール +> 参照ファイル: references/common.md + references/rust.md + +--- + +# LeetCode 102 · Binary Tree Level Order Traversal — Rust版 + +--- + +## 1. 問題の分析 + +> 💡 **この問題は一言で言うと「木を上から下へ、同じ高さのノードをまとめてグループ化する問題」です。** + +``` + 3 ← 深さ0: [3] + / \ + 9 20 ← 深さ1: [9, 20] + / \ + 15 7 ← 深さ2: [15, 7] + +出力: [[3], [9, 20], [15, 7]] +``` + +**Rustで解く際に特に気をつけるべき点:** +LeetCode の Rust 環境では、ツリーノードは `Option>>` という複合型で表現されています。`Rc`(=参照カウント型の共有ポインタ)と `RefCell`(=実行時に借用チェックを行う内部可変性コンテナ)の組み合わせで「1つのノードを複数の親から共有できる」構造を表現しています。この型を安全に扱うパターンを正確に押さえることが、この問題のRust実装の核心です。 + +--- + +### 競技プログラミング視点での分析 + +- ノード数は最大 2000 なので O(n) 単一パスで十分 +- キュー(`VecDeque`、=両端から追加・取り出しができるキュー)を使い、各ノードをちょうど1回だけ処理する +- `Rc::clone()` は参照カウントのインクリメントのみで、データのコピーは行わないためオーバーヘッドは小さい + +### 業務開発視点での分析 + +- `Option>>` は「値がある/ない」を型で安全に表現する。Javaの `null` と異なり、取り出す前に `None` チェックが強制されるためNullPointerException相当のバグが起きない +- `borrow()` の呼び出しで実行時借用チェックが行われ、同時ミュータブルアクセスが自動的に防止される + +### Rust特有の考慮点 + +- **所有権の移動(move)を避ける**: `Rc::clone(&node)` は所有権を移さず参照カウントをインクリメントするだけ。`node.clone()` と書いても同じ意味だが `Rc::clone()` と書くことでRustの慣習では「安価なクローン」であることを明示できる +- **`borrow()` vs `borrow_mut()`**: 今回は読み取りのみなので `borrow()`(共有参照)を使う。`borrow_mut()`(排他参照)は不要 + +> 📖 **このセクションで登場した用語** +> +> - **`Rc`**:Reference Counted。参照カウント(=何箇所から参照されているかを数える)によって、1つの値を複数の場所から所有できるスマートポインタ。スレッドをまたぐ場合は `Arc` を使う +> - **`RefCell`**:通常はコンパイル時に行う借用チェックを実行時に行うコンテナ。これにより「コンパイル時には分からない条件分岐に応じた可変借用」を安全に実現できる +> - **内部可変性**:外から見ると不変(`&T`)なのに、内部だけ変更できる仕組み。`RefCell` がその代表例 +> - **参照カウント**:ある値を指しているポインタの数を記録し、0になったら自動的にメモリを解放する仕組み + +--- + +## 2. アルゴリズムアプローチ比較 + +> 💡 同じ問題でも解き方は複数あります。「速さ(時間計算量)」「メモリ」「Rustの所有権モデルとの相性」を合わせて比べます。 + +| アプローチ | 時間計算量 | 空間計算量 | Rust実装コスト | 安全性 | 可読性 | 備考 | +| -------------------- | ---------- | ---------- | -------------- | ------ | ------ | ------------------------------------------------ | +| **BFS + `VecDeque`** | O(n) | O(n) | 低 | 高 | 高 | ✅ 今回の選択 | +| DFS(再帰) | O(n) | O(n) | 中 | 高 | 中 | スタックオーバーフローリスク、深さ管理が必要 | +| DFS(反復) | O(n) | O(n) | 中 | 高 | 中 | `Vec` をスタックとして使う。深さ情報の追跡が必要 | + +**Rust固有の観点**: + +- BFS + `VecDeque` は「今の階のノード数分だけ `pop_front()` する」パターンで自然に階層分けができ、Rcの借用スコープも短く済む +- DFS再帰版は `Rc::clone` の呼び出し回数は同じだが、再帰の深さがツリーの高さに比例してスタックを消費するため、バランスの悪い木では不利 + +> 📖 **このセクションで登場した用語** +> +> - **`VecDeque`**:`std::collections::VecDeque`。両端キュー(=前からも後ろからも要素を追加・取り出しできるデータ構造)。`Vec` の先頭への挿入は O(n) だが `VecDeque` は O(1) でできる +> - **スタックオーバーフロー**:再帰が深くなりすぎてプログラムが使えるスタックメモリを使い尽くしてしまうエラー + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択したアプローチ**: **BFS(幅優先探索)+ `VecDeque`** +- **理由**: + - **DFS再帰を選ばなかった理由**: 深さ情報を引数として持ち回る設計が必要になり、`Rc::clone` のスコープが広がって借用チェッカーとの格闘が増える + - **DFS反復を選ばなかった理由**: `Vec` をスタックとして使うと「今の階」の境界を別途追跡しなければならず、コードの見通しが悪くなる + - **BFSを選んだ理由**: `VecDeque` の長さを「今の階のサイズ」として使うことで、深さ情報を持ち回らずに階層分けが自然に実現できる + +- **Rust特有の最適化ポイント**: + - `Rc::clone(&node)` は参照カウントのインクリメントのみ。ヒープアロケーション(=ヒープ上に新しくメモリを確保する操作)は発生しない + - `borrow()` の返す `Ref`(=`RefCell` の共有借用ガード)はスコープを出ると自動的に解放されるため、借用の管理が明確 + +> 📖 **このセクションで登場した用語** +> +> - **ゼロコスト抽象化**:便利な高レベルな書き方をしても、手書きの低レベルコードと同じ速さになるRustの特性 +> - **`Ref`**:`RefCell` の `borrow()` が返す型。スコープを抜けると自動的に借用が解放されるRAIIガード + +--- + +## 4. 実装コード + +> 💡 **コードの大まかな骨格** +> +> 1. `root` が `None` なら空 `Vec` を即返す +> 2. `VecDeque` にルートを積んで BFS 開始 +> 3. ループ毎に「今の階のサイズ」を `len()` で固定し、その分だけ `pop_front()` +> 4. 各ノードの値を今の階の配列に追加し、`left`/`right` の子があれば次の階としてキューに積む +> 5. 階ごとの配列を `result` に追加して返す + +```rust +use std::cell::RefCell; +use std::collections::VecDeque; +use std::rc::Rc; + +// LeetCode が提供する TreeNode 定義(提出時はそのまま使う) +// #[derive(Debug, PartialEq, Eq)] +// pub struct TreeNode { +// pub val: i32, +// pub left: Option>>, +// pub right: Option>>, +// } + +impl Solution { + pub fn level_order(root: Option>>) -> Vec> { + // ─────────────────────────────────────────────────────────────── + // ① root が None(木が空)の場合は空ベクタを返す + // Option を「中身があるかないかが分かる箱」として扱う。 + // Javaの null と異なり、None チェックを忘れるとコンパイルエラーになるため + // ここで弾き忘れることが構造上ありえない。 + // ─────────────────────────────────────────────────────────────── + let root = match root { + None => return vec![], // None なら即終了 + Some(node) => node, // Some なら中身(Rc>)を取り出す + }; + + // ─────────────────────────────────────────────────────────────── + // ② 最終的な結果を格納する2次元ベクタ + // result[0] = 深さ0のノード値の配列、result[1] = 深さ1 ... となる + // ─────────────────────────────────────────────────────────────── + let mut result: Vec> = Vec::new(); + + // ─────────────────────────────────────────────────────────────── + // ③ 両端キュー(VecDeque)を用意し、ルートノードを入れる + // VecDeque を使う理由:Vec の先頭取り出しは O(n) だが + // VecDeque の pop_front() は O(1) で済むため、キューとして最適 + // ─────────────────────────────────────────────────────────────── + let mut queue: VecDeque>> = VecDeque::new(); + queue.push_back(root); // ルートをキューに追加 + + // ─────────────────────────────────────────────────────────────── + // ④ キューが空になるまでループ(= 全ノードを処理し終えるまで) + // ─────────────────────────────────────────────────────────────── + while !queue.is_empty() { + // 今この瞬間のキューの長さ = 「現在の階のノード数」 + // この値を先に固定するのが BFS の核心。 + // ループ中に queue.len() は変化するため、変数に保存しておく。 + let level_size = queue.len(); + + // 今の階のノード値を格納する一時ベクタ + let mut level_values: Vec = Vec::with_capacity(level_size); + // with_capacity(n) は「n個分のメモリを事前に確保」する。 + // push のたびに再アロケーションが起きるのを防ぐため。 + + // 今の階のノードを「level_size 個分」だけ取り出す + for _ in 0..level_size { + // pop_front() でキューの先頭からノードを O(1) で取り出す + // while !is_empty() のループ内なので必ず Some になるが、 + // 安全のため unwrap() ではなく if let で受け取る + let node_rc = queue.pop_front().unwrap(); + // unwrap() を使う根拠:直上の is_empty() チェックにより + // pop_front() が None を返すことはこのスコープでありえない + + // ────────────────────────────────────────────────────── + // ⑤ RefCell の borrow() で共有参照を取得する + // borrow() は「読み取り専用の貸し出し」を意味する。 + // borrow_mut() は「書き込み可能な貸し出し」で、同時に + // 1つしか存在できない(実行時に panic する)。 + // 今回は読み取りのみなので borrow() で十分。 + // ────────────────────────────────────────────────────── + let node = node_rc.borrow(); + // node は Ref 型。スコープを抜けると自動解放される。 + + // ノードの値を今の階の配列に追加 + level_values.push(node.val); + + // ────────────────────────────────────────────────────── + // ⑥ 子ノードをキューに追加する(次の階の準備) + // Rc::clone(&child) は所有権を移さず参照カウントを増やすだけ。 + // 「clone」という名前だがヒープ上のデータはコピーされない(安価)。 + // JavaやPythonでの参照コピーと同じ感覚で使える。 + // ────────────────────────────────────────────────────── + if let Some(left) = &node.left { + queue.push_back(Rc::clone(left)); + } + if let Some(right) = &node.right { + queue.push_back(Rc::clone(right)); + } + // node(Ref)はここでスコープを抜け、borrow が解放される + } + + // 今の階の値配列を結果に追加 + result.push(level_values); + } + + result + } +} +``` + +--- + +### 🔍 動作トレース(`root = [3, 9, 20, null, null, 15, 7]`) + +``` +ツリー: + 3 + / \ + 9 20 + / \ + 15 7 + +初期状態: + queue = [Node(3)] + result = [] + +━━━━━━━━━━ while ループ 1回目(深さ0) ━━━━━━━━━━ + level_size = 1 + level_values = [] + + i=0: pop_front() → node_rc = Rc queue = [] + node = node_rc.borrow() → val=3 + level_values.push(3) → [3] + left = Some(Node(9)) → Rc::clone → queue.push_back queue = [Node(9)] + right = Some(Node(20)) → Rc::clone → queue.push_back queue = [Node(9), Node(20)] + node(Ref)スコープ終了 → borrow 解放 + + result.push([3]) → result = [[3]] + +━━━━━━━━━━ while ループ 2回目(深さ1) ━━━━━━━━━━ + level_size = 2 + level_values = [] + + i=0: pop_front() → node_rc = Rc queue = [Node(20)] + node.val = 9 level_values = [9] + left = None → スキップ + right= None → スキップ + + i=1: pop_front() → node_rc = Rc ← 参照カウント = 1(queue が所持) + +Rc::clone(&node.left) 後: + Rc ← 参照カウント = 2(旧 queue + 新 queue が所持) + +pop_front() で旧 queue から取り出され、スコープ終了時: + Rc ← 参照カウント = 1(新 queue のみ) + → カウントが 0 にならない限りメモリは解放されない + → カウントが 0 になったとき初めて Drop(メモリ解放)が呼ばれる +``` + +--- + +### ✅ LeetCode 提出フォーマット(そのままコピー可) + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 2.39 MB +// Beats 87.85% + +use std::cell::RefCell; +use std::collections::VecDeque; +use std::rc::Rc; + +impl Solution { + pub fn level_order(root: Option>>) -> Vec> { + let root = match root { + None => return vec![], + Some(node) => node, + }; + + let mut result: Vec> = Vec::new(); + let mut queue: VecDeque>> = VecDeque::new(); + queue.push_back(root); + + while !queue.is_empty() { + let level_size = queue.len(); + let mut level_values: Vec = Vec::with_capacity(level_size); + + for _ in 0..level_size { + let node_rc = queue.pop_front().unwrap(); + let node = node_rc.borrow(); + level_values.push(node.val); + + if let Some(left) = &node.left { + queue.push_back(Rc::clone(left)); + } + if let Some(right) = &node.right { + queue.push_back(Rc::clone(right)); + } + } + + result.push(level_values); + } + + result + } +} +``` + +> 📖 **このセクションで登場した用語** +> +> - **`VecDeque::pop_front()`**:キューの先頭要素を O(1) で取り出し、`Option` で返すメソッド。`Vec::remove(0)` は O(n) なので絶対に使わない +> - **`Vec::with_capacity(n)`**:n 個分のメモリを事前確保してベクタを作る。後から `push` しても再アロケーションが発生しない +> - **`Ref`**:`RefCell::borrow()` が返す型。RAII(=スコープを抜けると自動でリソースを解放する仕組み)により、スコープ終了時に借用ガードが自動解放される +> - **`if let Some(x) = &opt`**:`Option` が `Some` のときだけ中身を取り出してブロックを実行するパターンマッチの省略形。`match` の代替として読みやすい +> - **RAII**:Resource Acquisition Is Initialization。変数のスコープと資源(メモリ・ファイル・ロックなど)の有効期間を一致させるRustの根本的な設計思想 + +--- + +## 5. 計算量まとめ + +| 項目 | 値 | 理由 | +| -------------- | ---- | ------------------------------------------------------------------------------- | +| **時間計算量** | O(n) | 各ノードをキューへの追加・取り出しで1回ずつ処理。`Rc::clone` は O(1) | +| **空間計算量** | O(n) | キューに最大で「最も広い階のノード数」が入る。最悪 n/2 個(完全二分木の最下層) | diff --git a/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Typescript.md b/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Typescript.md new file mode 100644 index 00000000..138f85ee --- /dev/null +++ b/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/Binary_Tree_Level_Order_Traversal_Typescript.md @@ -0,0 +1,292 @@ +> 🎯 **[algo-beginner スキル発火]** +> 言語/カテゴリ: TypeScript +> 適用ルールセット: 共通5ルール + TS固有5ルール +> 参照ファイル: references/common.md + references/typescript.md + +--- + +# LeetCode 102 · Binary Tree Level Order Traversal + +--- + +## 1. 問題の分析 + +> 💡 **この問題は一言で言うと「木を上から下へ、同じ高さのノードをまとめてグループ化する問題」です。** +> 木の「同じ深さ(階層)」にあるすべての値をひとつの配列にまとめ、その配列を深さ順に並べた2次元配列を返します。 + +--- + +### 🌳 問題が要求していること(視覚的確認) + +``` + 3 ← 深さ0: [3] + / \ + 9 20 ← 深さ1: [9, 20] + / \ + 15 7 ← 深さ2: [15, 7] + +出力: [[3], [9, 20], [15, 7]] +``` + +--- + +### 競技プログラミング視点での分析 + +- ノード数は最大 2000 なので、O(n) のアルゴリズムで十分に余裕がある +- 各ノードを **ちょうど1回だけ** 訪問する手法が理想 +- 追加メモリは出力配列のみに抑えたい + +### 業務開発視点での分析 + +- `TreeNode | null` という **Union型(=複数の型のうちどれかを表す型)** を安全に扱う必要がある +- `null` チェックを怠ると実行時エラーになるため、型ガード(=実行時に型を絞り込む条件分岐)が必須 +- 結果配列はイミュータブル(=変更不可)に構築して副作用を防ぐ + +### TypeScript特有の考慮点 + +- `TreeNode | null` の null 安全性を TypeScript のコンパイラに保証させる +- キュー(後述)の要素型を明示することで、取り出した要素が必ず `TreeNode` 型であると保証できる + +> 📖 **このセクションで登場した用語** +> +> - **Union型**:`A | B` のように「AまたはB」を表す TypeScript の型 +> - **null安全性**:`null` や `undefined` によるクラッシュをコンパイル時に防ぐ仕組み +> - **型ガード**:`if (node !== null)` のような条件で、その後のコードブロック内の型を自動的に絞り込む仕組み + +--- + +## 2. アルゴリズムアプローチ比較 + +> 💡 同じ問題でも解き方は複数あります。「速さ(時間計算量)」と「メモリの使い方(空間計算量)」を比べて最適なものを選びます。 + +| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +| -------------------------- | ---------- | ---------- | ------------ | -------- | ------ | -------------------------------- | +| **BFS(幅優先探索)** | O(n) | O(n) | 低 | 高 | 高 | ✅ 今回の選択 | +| DFS(深さ優先探索) | O(n) | O(n) | 中 | 高 | 中 | 再帰でも実装可能だが直感的でない | +| 総当たり(各深さでループ) | O(n²) | O(n) | 高 | 中 | 低 | 非推奨 | + +> 💡 **BFS(幅優先探索)を例え話で理解する** +> BFS は「同じ階のすべての部屋を開けてから、次の階に進むエレベーター」のようなものです。 +> 木でいえば「同じ深さのノードをすべて処理してから、次の深さへ進む」動き方です。 +> これを実現するのが **キュー(=先に入れたものを先に出す「行列」のデータ構造)** です。 + +> 📖 **このセクションで登場した用語** +> +> - **BFS(幅優先探索)**:グラフや木を「横方向に広がりながら」探索する方法 +> - **DFS(深さ優先探索)**:グラフや木を「縦方向に深く潜りながら」探索する方法 +> - **キュー**:先に入れたものを先に出す(FIFO: First In, First Out)データ構造。銀行の窓口の行列と同じ + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択したアプローチ**: **BFS(幅優先探索)+ キュー** +- **理由**: + - **DFS を選ばなかった理由**: DFS は縦に深く潜るため、「同じ階のノードをまとめる」処理と相性が悪く、深さ情報を別途管理する手間が増える + - **総当たりを選ばなかった理由**: ノードを重複して走査するため O(n²) になり、ノード数 2000 でも無駄が大きい + - **BFS を選んだ理由**: キューから「今の階のノード数分だけ」取り出すことで、自然に「1階ぶんのグループ」が作れる + +- **TypeScript特有の最適化ポイント**: + - キューの型を `TreeNode[]` と明示することで、取り出した要素が必ず `TreeNode` 型になりコンパイル時に安全を保証 + - `node.left` / `node.right` は `TreeNode | null` なので、`!== null` チェックで null を排除してからキューに追加する + +> 📖 **このセクションで登場した用語** +> +> - **コンパイル時保証**:TypeScript がコードを変換する段階でエラーを検出すること。実行前にバグを防げる +> - **FIFO**:First In, First Out。最初に入れたものを最初に取り出す順序 + +--- + +## 4. 実装コード + +> 💡 **コードの大まかな骨格**(コードを読む前にこの構造を頭に入れてください) +> +> 1. `root` が `null` なら即座に空配列 `[]` を返す +> 2. `root` をキューに入れて探索開始 +> 3. キューが空になるまで繰り返す +> - 今の階のノード数(`levelSize`)を記録する +> - ちょうど `levelSize` 個のノードを取り出し、その値を今の階の配列に追加 +> - 取り出したノードの左・右の子があればキューに追加(次の階の準備) +> 4. 各階の配列を `result` に追加して返す + +```typescript +// Runtime 2 ms +// Beats 39.72% +// Memory 60.20 MB +// Beats 57.23% + +function levelOrder(root: TreeNode | null): number[][] { + // ───────────────────────────────────────────────────────── + // ① root が null(木が空)の場合は空配列を返す + // 後続でキューにアクセスするため、ここで弾かないと + // null に対して .left などへアクセスしてクラッシュする + // ───────────────────────────────────────────────────────── + if (root === null) return []; + + // ───────────────────────────────────────────────────────── + // ② 結果を格納する2次元配列を用意する + // result[0] = 深さ0のノード値の配列、 + // result[1] = 深さ1のノード値の配列、... となる + // ───────────────────────────────────────────────────────── + const result: number[][] = []; + + // ───────────────────────────────────────────────────────── + // ③ キュー(行列)を用意し、root を最初の要素として入れる + // 型を TreeNode[] と明示することで、取り出した要素が + // null でないことをコンパイラが保証してくれる + // (null をキューに入れないよう、後続の追加時にチェックする) + // ───────────────────────────────────────────────────────── + const queue: TreeNode[] = [root]; + + // ───────────────────────────────────────────────────────── + // ④ キューが空になるまでループ(= 全ノードを処理し終えるまで) + // ───────────────────────────────────────────────────────── + while (queue.length > 0) { + // ── 今この瞬間のキューの長さ = 「現在の階のノード数」 ── + // この値を先に固定するのが BFS の核心。 + // ループ中に queue.length は変化するため、 + // 先に変数に保存しておかないと「今の階」の範囲がズレる。 + const levelSize: number = queue.length; + + // 今の階のノード値を格納する一時配列 + const levelValues: number[] = []; + + // 今の階のノードを「levelSize 個分」だけ取り出す + for (let i = 0; i < levelSize; i++) { + // shift() でキューの先頭からノードを取り出す(FIFO) + // queue の要素は TreeNode[] なので null にならない(③で保証) + const node = queue.shift()!; // ! は「nullでないことをここで断言」 + + // 取り出したノードの値を今の階の配列に追加 + levelValues.push(node.val); + + // ── 次の階の準備:子ノードをキューの末尾に追加する ── + // null でない場合だけ追加する(null をキューに入れないルール) + if (node.left !== null) { + queue.push(node.left); + } + if (node.right !== null) { + queue.push(node.right); + } + } + + // 今の階分の値配列を結果に追加 + result.push(levelValues); + } + + return result; +} +``` + +--- + +### 🔍 動作トレース(具体的な入力例での変数変化) + +**入力**: `root = [3, 9, 20, null, null, 15, 7]`(ツリー構造は下記) + +``` + 3 + / \ + 9 20 + / \ + 15 7 +``` + +``` +初期状態: + queue = [Node(3)] + result = [] + +━━━━━━━━━━ while ループ 1回目(深さ0) ━━━━━━━━━━ + levelSize = 1 ← 今の階のノード数は1 + levelValues = [] + + i=0: node = queue.shift() → Node(3) queue = [] + levelValues.push(3) → [3] + node.left = Node(9) → queue.push(Node(9)) queue = [Node(9)] + node.right = Node(20) → queue.push(Node(20)) queue = [Node(9), Node(20)] + + result.push([3]) → result = [[3]] + +━━━━━━━━━━ while ループ 2回目(深さ1) ━━━━━━━━━━ + levelSize = 2 ← 今の階のノード数は2 + levelValues = [] + + i=0: node = queue.shift() → Node(9) queue = [Node(20)] + levelValues.push(9) → [9] + node.left = null → スキップ + node.right = null → スキップ + + i=1: node = queue.shift() → Node(20) queue = [] + levelValues.push(20) → [9, 20] + node.left = Node(15) → queue.push(Node(15)) queue = [Node(15)] + node.right = Node(7) → queue.push(Node(7)) queue = [Node(15), Node(7)] + + result.push([9,20]) → result = [[3], [9, 20]] + +━━━━━━━━━━ while ループ 3回目(深さ2) ━━━━━━━━━━ + levelSize = 2 ← 今の階のノード数は2 + levelValues = [] + + i=0: node = queue.shift() → Node(15) queue = [Node(7)] + levelValues.push(15) → [15] + node.left = null → スキップ + node.right = null → スキップ + + i=1: node = queue.shift() → Node(7) queue = [] + levelValues.push(7) → [15, 7] + node.left = null → スキップ + node.right = null → スキップ + + result.push([15,7]) → result = [[3], [9, 20], [15, 7]] + +━━━━━━━━━━ queue が空 → ループ終了 ━━━━━━━━━━ +戻り値: [[3], [9, 20], [15, 7]] ✅ +``` + +--- + +### ✅ LeetCode 提出フォーマット(そのままコピー可) + +```typescript +function levelOrder(root: TreeNode | null): number[][] { + if (root === null) return []; + + const result: number[][] = []; + const queue: TreeNode[] = [root]; + + while (queue.length > 0) { + const levelSize: number = queue.length; + const levelValues: number[] = []; + + for (let i = 0; i < levelSize; i++) { + const node = queue.shift()!; + levelValues.push(node.val); + + if (node.left !== null) queue.push(node.left); + if (node.right !== null) queue.push(node.right); + } + + result.push(levelValues); + } + + return result; +} +``` + +> 📖 **このセクションで登場した用語** +> +> - **`shift()`**:配列の先頭要素を取り出して返すメソッド。キューの「取り出し」操作に使う +> - **`push()`**:配列の末尾に要素を追加するメソッド。キューの「追加」操作に使う +> - **`!`(非null断言)**:TypeScriptに「この値は絶対 null でない」と伝える記号。型チェックを強制的に通す代わりに、間違えると実行時エラーになるため、確信がある箇所にのみ使う +> - **FIFO**:First In First Out。先に入れたものを先に取り出す順序。キューはこの性質を持つ +> - **`readonly`(今回不使用だが参考)**:変数を変更不可にする TypeScript の修飾子。JavaScript には存在せず、コンパイル時の書き換えを防ぐために使う + +--- + +## 5. 計算量まとめ + +| 項目 | 値 | 理由 | +| -------------- | ---- | --------------------------------------------------------------------------------- | +| **時間計算量** | O(n) | 各ノードをキューへの追加・取り出しでちょうど1回ずつ処理するため | +| **空間計算量** | O(n) | キューに最大で「木の最も広い階のノード数」が入る。最悪ケースは全ノード数 n に比例 | diff --git a/public/index.html b/public/index.html index 03bb0e76..0bae42ab 100644 --- a/public/index.html +++ b/public/index.html @@ -819,7 +819,7 @@

🧪 - Generated on 2026-03-20 + Generated on 2026-04-10
+ + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+ +
+

💡 この問題を一言で言うと:

+

+ 「木の同じ深さ(階層)にあるノードを全部集めて1つのリストにまとめ、それを深さ順に並べた2次元配列を返す問題」です。
+ 左から右の順番でノードを集める必要があります。 +

+
+ +
+

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

+
    +
  • + 「縦方向(深さ優先)」に探索するだけでは、同じ深さのノードをまとめる境界が分からない +
  • +
  • + list.pop(0) + を使うと先頭削除が O(n) になり全体が O(n²) へ悪化する +
  • +
  • + ノードの値が + 0 + のとき、or + トリックは + 0(falsy)と誤判定して壊れる +
  • +
+
+ +
+
+
O(n)
+
時間計算量
+
+
+
O(n)
+
空間計算量
+
+
+
+ deque +
+
データ構造
+
+
+
BFS
+
探索手法
+
+
+ +
+

📥 入出力例

+
+
+

入力(ツリー)

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

出力と理由

+
+[[3],[9,20],[15,7]]
+
+深さ0 → [3]
+深さ1 → [9, 20]  ← 左から右
+深さ2 → [15, 7]  ← 左から右
+
+
+
+ +
+

📋 制約

+
    +
  • ノード数:0 以上 2000 以下
  • +
  • ノードの値:-1000 以上 1000 以下(0 が含まれる)
  • +
+
+
+ + +
+

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

+

+ 各ステップをクリックするか、▶ Play ボタンで自動再生できます。 +

+
+
+ + +
+

+ Python 実装 +

+ +
+

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

+
    +
  1. + 空ツリーのチェック:root が None なら即座に [] を返す +
  2. +
  3. + deque の初期化:root をキューに入れて BFS 開始の準備 +
  4. +
  5. + while ループ:キューが空になるまで、階層ごとに処理する +
  6. +
  7. + level_size の固定:今の階のサイズをここで確定させる(核心) +
  8. +
  9. + for ループ:level_size 個だけ popleft() + し、値収集と子の登録を行う +
  10. +
  11. 結果への追加:今の階の値リストを result に追加する
  12. +
+
+ +
from collections import deque
+from typing import Optional
+
+
+class Solution:
+    def levelOrder(self, root: Optional[TreeNode]) -> list[list[int]]:
+        # 基底条件: root が None(空ツリー)なら即座に空リストを返す
+        # None チェックをしないと後続の node.val アクセスでクラッシュする
+        if root is None:
+            return []
+
+        result: list[list[int]] = []
+
+        # collections.deque をキューとして使う
+        # list.pop(0) は O(n) だが deque.popleft() は O(1)
+        # deque は C言語実装の双方向キューで先頭・末尾への操作が高速
+        queue: deque[TreeNode] = deque([root])
+
+        # キューが空になるまでループ(= 全ノードを処理し終えるまで)
+        while queue:
+            # ── BFS の核心:今の階のサイズをここで固定する ──
+            # ループ中に popleft()/append() で queue の長さが変化するため
+            # 先に変数へ保存しないと「今の階」の範囲がずれて Wrong Answer になる
+            level_size: int = len(queue)
+            level_values: list[int] = []
+
+            for _ in range(level_size):
+                # popleft() でキューの先頭ノードを O(1) で取り出す
+                node: TreeNode = queue.popleft()
+
+                # val を直接 append する(0 も正しく追加される)
+                # `or` トリックは val=0 のとき falsy と判定されて壊れるため使わない
+                level_values.append(node.val)
+
+                # 子が存在する場合のみキューへ追加(次の階の準備)
+                if node.left is not None:
+                    queue.append(node.left)
+                if node.right is not None:
+                    queue.append(node.right)
+
+            result.append(level_values)
+
+        return result
+ +
+

+ ▶ 入力例 [3, 9, 20, null, null, 15, 7] での動作トレース +

+
+初期状態: queue = deque([Node(3)]),  result = []
+
+━━ ループ 1回目(深さ0) ━━
+  level_size = 1   ← ここで固定!
+  vals = []
+  i=0: popleft() → Node(3)   queue = deque([])
+       append(3)  → vals = [3]
+       left=Node(9)  → queue = deque([Node(9)])
+       right=Node(20) → queue = deque([Node(9), Node(20)])
+  result = [[3]]
+
+━━ ループ 2回目(深さ1) ━━
+  level_size = 2   ← ここで固定!
+  i=0: Node(9)  → vals=[9]       子なし
+  i=1: Node(20) → vals=[9,20]    left=Node(15), right=Node(7)
+  result = [[3], [9, 20]]
+
+━━ ループ 3回目(深さ2) ━━
+  level_size = 2
+  i=0: Node(15) → vals=[15]      子なし
+  i=1: Node(7)  → vals=[15,7]    子なし
+  result = [[3], [9, 20], [15, 7]]
+
+queue が空 → ループ終了
+戻り値: [[3], [9, 20], [15, 7]] ✅
+
+
+ + +
+

+ 処理フローチャート +

+ + +
+

+ 🗺️ フローチャートの読み方(Mermaid 記法) +

+
+
+ ([…]) + スタジアム形(緑)
= 開始・終了
+
+
+ […] + 四角形(青)
= 処理ステップ
+
+
+ {…} + ひし形(黄)
= 条件分岐
+
+
+ + Yes(はい) + No(いいえ) + +
+
+
+ + +
+
+ 読み込み中… +
+
+ + +
+ + 緑 = + 開始・終了ノード + + + 青 = + 通常の処理 + + + 黄 = + 条件分岐 + + + 紫 = + BFS の核心処理 + +
+ + +
+

+ 🔎 入力例 [3, 9, 20, null, null, 15, 7] でのフロー追跡 +

+
    +
  1. 「Start」 → root=Node(3) を受け取る
  2. +
  3. + 「root is None?」 → いいえ → 初期化: result=[], queue=deque([Node(3)]) +
  4. +
  5. + 「queue は空?」 → いいえ(Node(3) がある)→ + level_size=1, vals=[] +
  6. +
  7. + 「i < 1?」 → はい → popleft()=Node(3), vals=[3], left=Node(9)→追加, + right=Node(20)→追加 +
  8. +
  9. 「i < 1?」 → いいえ → result.append([3]) → ループバック
  10. +
  11. + (深さ1)level_size=2, + Node(9)→vals=[9], Node(20)→vals=[9,20], Node(15)&Node(7)追加 +
  12. +
  13. + (深さ2)level_size=2, + Node(15)→vals=[15], Node(7)→vals=[15,7] +
  14. +
  15. + 「queue は空?」 → はい → 「return result」へ → [[3],[9,20],[15,7]] ✅ +
  16. +
+
+
+ + +
+

+ 計算量分析 +

+ +
+

+ 📖 Big-O 記法の読み方(n = ノード数) +

+
+
+
O(1)
+
+ 常に一定
例:辞書の直接引き +
+
+
+
O(n)
+
+ 入力に比例
例:リストを1回走査 +
+
+
+
O(n²)
+
+ 入力の2乗
例:二重ループ総当たり +
+
+
+
list.pop(0)
+
+ 先頭削除は O(n)
全体 O(n²) になる +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
種別計算量理由
時間計算量O(n) + 各ノードを popleft() で1回だけ処理(O(1)×n回) +
空間計算量O(n) + キューに最大で最下層のノード数(最大 n/2 個)が入る +
+ 比較:list.pop(0) + O(n²) + 先頭削除のたびに残り全要素をシフト(O(n)×n回) +
+
+ +
+

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

+

+ 全ノード数を n とすると、各ノードはキューへの + append()(O(1))と + popleft()(O(1))を1回ずつ行うため、 合計操作回数は 2n = + O(n) です。 空間については、完全二分木の最下層に最大 n/2 + 個のノードが集まるため、 キューの最大サイズは O(n) となります。 + これは出力配列 result のサイズも同様で、全ノードの値を格納するため O(n) + です。 +

+
+
+ + +
+

+ 📖 用語集 +

+

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

+
+
+ + BFS(幅優先探索) + +
+ Breadth-First Search + の略。グラフや木を「横方向に広がりながら」探索する方法。 + 同じ深さのノードをすべて処理してから次の深さへ進む。 + 「エレベーターで同じ階を全部回ってから次の階へ行く」イメージ。 + この問題では「同じ階層のノードをまとめる」処理と自然に一致するため選ばれる。 +
+
+ +
+ + Big-O 記法 + +
+ アルゴリズムの「入力サイズが大きくなるにつれて処理時間やメモリがどう増えるか」を表す記法。 + O(1) は常に一定、O(n) は入力に比例、O(n²) は入力の2乗で増加する。 + 実際の秒数ではなく「増え方の傾向」を表す。 +
+
+ +
+ + deque(デック) + +
+ collections.deque + のこと。 Doubly-Ended Queue(両端キュー)の略。前からも後ろからも O(1) + で出し入れできる「両端開きの箱」。 Python の + list + の先頭削除は O(n) だが、 deque の + popleft() は + O(1)。 C言語で実装されており高速。 +
+
+ +
+ + falsy(フォールシー) + +
+ Python の + if + の条件式で + False + 相当と見なされる値。 + 0None[]"" + などが該当する。 ノードの値 + val = 0 は + falsy なので、 + 0 or 式 + という書き方は「0のとき右辺を実行する」という誤動作を引き起こす。 +
+
+ +
+ + FIFO(先入れ先出し) + +
+ First In, First Out の略。最初に入れたものを最初に取り出す順序。 + 銀行の窓口の行列と同じ仕組み。キューはこの性質を持つ。 BFS + はこの性質を利用して「深さが浅いノードから順番に処理する」を実現する。 +
+
+ +
+ + キュー(Queue) + +
+ 先に入れたものを先に取り出す(FIFO)データ構造。 + 銀行の窓口の行列と同じ。 BFS + では「処理待ちのノード」をキューに積み、先頭から順番に処理することで + 「浅い順」に探索できる。 +
+
+ +
+ + + level_size(階層サイズの固定) + +
+ BFS の while ループの各反復の開始時に + level_size = len(queue) + で 「今の階のノード数」を変数に保存するテクニック。 ループ中に子の追加で + queue の長さが変化するため、先に固定しないと + 「今の階」と「次の階」の境界が混在して Wrong Answer になる。 +
+
+ +
+ + 二分木(Binary Tree) + +
+ 各ノードが「左の子」と「右の子」の最大2つを持つ木構造のデータ。 + 木の頂点を「根(root)」と呼び、子を持たないノードを「葉(leaf)」と呼ぶ。 + 根からあるノードまでの辺の数を「深さ(depth)」と呼ぶ。根の深さは 0。 +
+
+ +
+ + popleft() + +
+ deque + の先頭要素を O(1) で取り出すメソッド。 + list.pop(0) + は残り全要素を前にシフトするため O(n) かかるが、 + deque.popleft() + は双方向連結リストの先頭ポインタを1つ進めるだけなので O(1)。 n=2000 + のとき、この差は 2000 回 vs 最大 4,000,000 回の操作差になる。 +
+
+
+
+ +
+ LeetCode #102 · Binary Tree Level Order Traversal · Python BFS 解説 +
+
+ + + + + + + + diff --git a/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/README_react.html b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/README_react.html new file mode 100644 index 00000000..a235a09a --- /dev/null +++ b/public/Algorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order Traversal/README_react.html @@ -0,0 +1,1341 @@ + + + + + + LeetCode 102 · Binary Tree Level Order Traversal + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+ +
+

💡 この問題を一言で言うと:

+

+ 「木の同じ深さ(階層)にあるノードを全部集めて1つのリストにまとめ、それを深さ順に並べた2次元配列を返す問題」です。
+ 左から右の順番でノードを集める必要があります。 +

+
+ +
+

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

+
    +
  • + 「縦方向(深さ優先)」に探索するだけでは、同じ深さのノードをまとめる境界が分からない +
  • +
  • + list.pop(0) + を使うと先頭削除が O(n) になり全体が O(n²) へ悪化する +
  • +
  • + ノードの値が + 0 + のとき、or + トリックは + 0(falsy)と誤判定して壊れる +
  • +
+
+ +
+
+
O(n)
+
時間計算量
+
+
+
O(n)
+
空間計算量
+
+
+
+ deque +
+
データ構造
+
+
+
BFS
+
探索手法
+
+
+ +
+

📥 入出力例

+
+
+

入力(ツリー)

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

出力と理由

+
+[[3],[9,20],[15,7]]
+
+深さ0 → [3]
+深さ1 → [9, 20]  ← 左から右
+深さ2 → [15, 7]  ← 左から右
+
+
+
+ +
+

📋 制約

+
    +
  • ノード数:0 以上 2000 以下
  • +
  • ノードの値:-1000 以上 1000 以下(0 が含まれる)
  • +
+
+
+ + +
+

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

+

+ 各ステップをクリックするか、▶ Play ボタンで自動再生できます。 +

+
+
+ + +
+

+ Python 実装 +

+ +
+

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

+
    +
  1. + 空ツリーのチェック:root が None なら即座に [] を返す +
  2. +
  3. + deque の初期化:root をキューに入れて BFS 開始の準備 +
  4. +
  5. + while ループ:キューが空になるまで、階層ごとに処理する +
  6. +
  7. + level_size の固定:今の階のサイズをここで確定させる(核心) +
  8. +
  9. + for ループ:level_size 個だけ popleft() + し、値収集と子の登録を行う +
  10. +
  11. 結果への追加:今の階の値リストを result に追加する
  12. +
+
+ +
from collections import deque
+from typing import Optional
+
+
+class Solution:
+    def levelOrder(self, root: Optional[TreeNode]) -> list[list[int]]:
+        # 基底条件: root が None(空ツリー)なら即座に空リストを返す
+        # None チェックをしないと後続の node.val アクセスでクラッシュする
+        if root is None:
+            return []
+
+        result: list[list[int]] = []
+
+        # collections.deque をキューとして使う
+        # list.pop(0) は O(n) だが deque.popleft() は O(1)
+        # deque は C言語実装の双方向キューで先頭・末尾への操作が高速
+        queue: deque[TreeNode] = deque([root])
+
+        # キューが空になるまでループ(= 全ノードを処理し終えるまで)
+        while queue:
+            # ── BFS の核心:今の階のサイズをここで固定する ──
+            # ループ中に popleft()/append() で queue の長さが変化するため
+            # 先に変数へ保存しないと「今の階」の範囲がずれて Wrong Answer になる
+            level_size: int = len(queue)
+            level_values: list[int] = []
+
+            for _ in range(level_size):
+                # popleft() でキューの先頭ノードを O(1) で取り出す
+                node: TreeNode = queue.popleft()
+
+                # val を直接 append する(0 も正しく追加される)
+                # `or` トリックは val=0 のとき falsy と判定されて壊れるため使わない
+                level_values.append(node.val)
+
+                # 子が存在する場合のみキューへ追加(次の階の準備)
+                if node.left is not None:
+                    queue.append(node.left)
+                if node.right is not None:
+                    queue.append(node.right)
+
+            result.append(level_values)
+
+        return result
+ +
+

+ ▶ 入力例 [3, 9, 20, null, null, 15, 7] での動作トレース +

+
+初期状態: queue = deque([Node(3)]),  result = []
+
+━━ ループ 1回目(深さ0) ━━
+  level_size = 1   ← ここで固定!
+  vals = []
+  i=0: popleft() → Node(3)   queue = deque([])
+       append(3)  → vals = [3]
+       left=Node(9)  → queue = deque([Node(9)])
+       right=Node(20) → queue = deque([Node(9), Node(20)])
+  result = [[3]]
+
+━━ ループ 2回目(深さ1) ━━
+  level_size = 2   ← ここで固定!
+  i=0: Node(9)  → vals=[9]       子なし
+  i=1: Node(20) → vals=[9,20]    left=Node(15), right=Node(7)
+  result = [[3], [9, 20]]
+
+━━ ループ 3回目(深さ2) ━━
+  level_size = 2
+  i=0: Node(15) → vals=[15]      子なし
+  i=1: Node(7)  → vals=[15,7]    子なし
+  result = [[3], [9, 20], [15, 7]]
+
+queue が空 → ループ終了
+戻り値: [[3], [9, 20], [15, 7]] ✅
+
+
+ + +
+

+ 処理フローチャート +

+ + +
+

+ 🗺️ フローチャートの読み方(Mermaid 記法) +

+
+
+ ([…]) + スタジアム形(緑)
= 開始・終了
+
+
+ […] + 四角形(青)
= 処理ステップ
+
+
+ {…} + ひし形(黄)
= 条件分岐
+
+
+ + Yes(はい) + No(いいえ) + +
+
+
+ + +
+
+ 読み込み中… +
+
+ + +
+ + 緑 = + 開始・終了ノード + + + 青 = + 通常の処理 + + + 黄 = + 条件分岐 + + + 紫 = + BFS の核心処理 + +
+ + +
+

+ 🔎 入力例 [3, 9, 20, null, null, 15, 7] でのフロー追跡 +

+
    +
  1. 「Start」 → root=Node(3) を受け取る
  2. +
  3. + 「root is None?」 → いいえ → 初期化: result=[], queue=deque([Node(3)]) +
  4. +
  5. + 「queue は空?」 → いいえ(Node(3) がある)→ + level_size=1, vals=[] +
  6. +
  7. + 「i < 1?」 → はい → popleft()=Node(3), vals=[3], left=Node(9)→追加, + right=Node(20)→追加 +
  8. +
  9. 「i < 1?」 → いいえ → result.append([3]) → ループバック
  10. +
  11. + (深さ1)level_size=2, + Node(9)→vals=[9], Node(20)→vals=[9,20], Node(15)&Node(7)追加 +
  12. +
  13. + (深さ2)level_size=2, + Node(15)→vals=[15], Node(7)→vals=[15,7] +
  14. +
  15. + 「queue は空?」 → はい → 「return result」へ → [[3],[9,20],[15,7]] ✅ +
  16. +
+
+
+ + +
+

+ 計算量分析 +

+ +
+

+ 📖 Big-O 記法の読み方(n = ノード数) +

+
+
+
O(1)
+
+ 常に一定
例:辞書の直接引き +
+
+
+
O(n)
+
+ 入力に比例
例:リストを1回走査 +
+
+
+
O(n²)
+
+ 入力の2乗
例:二重ループ総当たり +
+
+
+
list.pop(0)
+
+ 先頭削除は O(n)
全体 O(n²) になる +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
種別計算量理由
時間計算量O(n) + 各ノードを popleft() で1回だけ処理(O(1)×n回) +
空間計算量O(n) + キューに最大で最下層のノード数(最大 n/2 個)が入る +
+ 比較:list.pop(0) + O(n²) + 先頭削除のたびに残り全要素をシフト(O(n)×n回) +
+
+ +
+

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

+

+ 全ノード数を n とすると、各ノードはキューへの + append()(O(1))と + popleft()(O(1))を1回ずつ行うため、 合計操作回数は 2n = + O(n) です。 空間については、完全二分木の最下層に最大 n/2 + 個のノードが集まるため、 キューの最大サイズは O(n) となります。 + これは出力配列 result のサイズも同様で、全ノードの値を格納するため O(n) + です。 +

+
+
+ + +
+

+ 📖 用語集 +

+

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

+
+
+ + BFS(幅優先探索) + +
+ Breadth-First Search + の略。グラフや木を「横方向に広がりながら」探索する方法。 + 同じ深さのノードをすべて処理してから次の深さへ進む。 + 「エレベーターで同じ階を全部回ってから次の階へ行く」イメージ。 + この問題では「同じ階層のノードをまとめる」処理と自然に一致するため選ばれる。 +
+
+ +
+ + Big-O 記法 + +
+ アルゴリズムの「入力サイズが大きくなるにつれて処理時間やメモリがどう増えるか」を表す記法。 + O(1) は常に一定、O(n) は入力に比例、O(n²) は入力の2乗で増加する。 + 実際の秒数ではなく「増え方の傾向」を表す。 +
+
+ +
+ + deque(デック) + +
+ collections.deque + のこと。 Doubly-Ended Queue(両端キュー)の略。前からも後ろからも O(1) + で出し入れできる「両端開きの箱」。 Python の + list + の先頭削除は O(n) だが、 deque の + popleft() は + O(1)。 C言語で実装されており高速。 +
+
+ +
+ + falsy(フォールシー) + +
+ Python の + if + の条件式で + False + 相当と見なされる値。 + 0None[]"" + などが該当する。 ノードの値 + val = 0 は + falsy なので、 + 0 or 式 + という書き方は「0のとき右辺を実行する」という誤動作を引き起こす。 +
+
+ +
+ + FIFO(先入れ先出し) + +
+ First In, First Out の略。最初に入れたものを最初に取り出す順序。 + 銀行の窓口の行列と同じ仕組み。キューはこの性質を持つ。 BFS + はこの性質を利用して「深さが浅いノードから順番に処理する」を実現する。 +
+
+ +
+ + キュー(Queue) + +
+ 先に入れたものを先に取り出す(FIFO)データ構造。 + 銀行の窓口の行列と同じ。 BFS + では「処理待ちのノード」をキューに積み、先頭から順番に処理することで + 「浅い順」に探索できる。 +
+
+ +
+ + + level_size(階層サイズの固定) + +
+ BFS の while ループの各反復の開始時に + level_size = len(queue) + で 「今の階のノード数」を変数に保存するテクニック。 ループ中に子の追加で + queue の長さが変化するため、先に固定しないと + 「今の階」と「次の階」の境界が混在して Wrong Answer になる。 +
+
+ +
+ + 二分木(Binary Tree) + +
+ 各ノードが「左の子」と「右の子」の最大2つを持つ木構造のデータ。 + 木の頂点を「根(root)」と呼び、子を持たないノードを「葉(leaf)」と呼ぶ。 + 根からあるノードまでの辺の数を「深さ(depth)」と呼ぶ。根の深さは 0。 +
+
+ +
+ + popleft() + +
+ deque + の先頭要素を O(1) で取り出すメソッド。 + list.pop(0) + は残り全要素を前にシフトするため O(n) かかるが、 + deque.popleft() + は双方向連結リストの先頭ポインタを1つ進めるだけなので O(1)。 n=2000 + のとき、この差は 2000 回 vs 最大 4,000,000 回の操作差になる。 +
+
+
+
+ +
+ LeetCode #102 · Binary Tree Level Order Traversal · Python BFS 解説 +
+
+ + + + + + + + diff --git a/public/index.html b/public/index.html index 0bae42ab..dd1088d8 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

168 interactive lessons across 6 domains

+

169 interactive lessons across 6 domains

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

- + @@ -467,6 +467,7 @@

  • 🧩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 102 · Binary Tree Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order 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
  • @@ -642,6 +643,7 @@

  • 🧩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 102 · Binary Tree Level Order TraversalAlgorithm/BinaryTree/claude sonnet 4.6 extended/102. Binary Tree Level Order 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
  • @@ -819,7 +821,7 @@

    🧪 - Generated on 2026-04-10 + Generated on 2026-04-11