Skip to content

Commit 042efac

Browse files
committed
Add LeetCode 103 Binary Tree Zigzag Level Order Traversal solutions
1 parent d8dc789 commit 042efac

7 files changed

Lines changed: 5374 additions & 6 deletions

File tree

Algorithm/BinaryTree/claude sonnet 4.6 extended/103. Binary Tree Zigzag Level Order Traversal/Binary_Tree_Zigzag_Level_Order_Traversal_Python.md

Lines changed: 452 additions & 0 deletions
Large diffs are not rendered by default.

Algorithm/BinaryTree/claude sonnet 4.6 extended/103. Binary Tree Zigzag Level Order Traversal/Binary_Tree_Zigzag_Level_Order_Traversal_Rust.md

Lines changed: 370 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
> 🎯 **[algo-beginner スキル発火]**
2+
> 言語/カテゴリ: TypeScript
3+
> 適用ルールセット: 共通5ルール + TS固有5ルール
4+
> 参照ファイル: references/common.md + references/typescript.md
5+
6+
---
7+
8+
# 1. 問題の分析
9+
10+
> 💡 **この問題は一言で言うと**「二分木(=各ノードが最大2つの子を持つ木構造)を階層ごとに読み取り、偶数階層は左→右、奇数階層は右→左と**ジグザグ**に読む問題」です。
11+
12+
## 競技プログラミング視点での分析
13+
14+
通常のレベルオーダー(幅優先探索=BFS)に「偶数階層か奇数階層かで読む向きを反転する」処理を追加します。
15+
16+
- 全ノードを一度だけ訪問すれば解けるため、時間計算量(=処理にかかる手間の目安)は **O(n)** が目標
17+
- キューに保持するノード数は「最も幅が広い階層のノード数」が上限 → 完全二分木なら最大 `n/2` ノード
18+
19+
## 業務開発視点での分析
20+
21+
- `TreeNode | null` という **Union型(=複数の型のどちらかを表す型)** を通じてnull安全性を確保
22+
- `root``null`(空ツリー)のケースは最初にガードして早期リターンする
23+
- 各階層の結果を `number[][]` に格納するため、型が明確で保守しやすい
24+
25+
## TypeScript特有の考慮点
26+
27+
- LeetCode環境では `TreeNode` クラスが外部から定義されているため、ジェネリクスは使わず `number` 固定で問題なし
28+
- `readonly``const assertion` より**可読性を最優先**にした実装が現場にもマッチする
29+
30+
> 📖 **このセクションで登場した用語**
31+
>
32+
> - **BFS(幅優先探索)**:木やグラフを「階層ごと」に左から右へ探索する方法。キュー(行列)を使う
33+
> - **Union型**`A | B` のように「AかBのどちらかの型」を表すTypeScript独自の型表現
34+
> - **null安全性**`null``undefined` への予期せぬアクセスでクラッシュしないように守る仕組み
35+
36+
---
37+
38+
# 2. アルゴリズムアプローチ比較
39+
40+
> 💡 同じ問題でも解き方は複数あります。それぞれの「速さ(時間計算量)」と「メモリの使いやすさ(空間計算量)」を比べ、最適なものを選びます。
41+
42+
| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |
43+
| ------------------------------------------------ | ---------- | ----------- | ------------ | -------- | ------ | ------------------------------------- |
44+
| **BFS + 偶奇で reverse** | O(n) | O(n) ||| ⭐高 | 最もシンプル・直感的 |
45+
| BFS + 両端キュー(deque)で先頭/末尾交互挿入 | O(n) | O(n) |||| JS標準にdequeがなく実装が煩雑 |
46+
| DFS(深さ優先探索)+ 各階層にpush | O(n) | O(n) + O(h) |||| 再帰スタックが木の高さ h 分追加される |
47+
| ブルートフォース(全ノードを配列に格納して整理) | O(n²) | O(n) |||| reverseが各階層で走り非効率 |
48+
49+
> 💡 **Big-O記法の読み方**
50+
>
51+
> - `O(n)`:ノード数が2倍になると処理も約2倍(一番無駄がない)
52+
> - `O(h)`:木の高さ h 分のスタック消費(最悪 O(n)、バランスが取れていれば O(log n))
53+
> 📖 **このセクションで登場した用語**
54+
> - **時間計算量**:ノード数に対して処理の手間がどう増えるかの目安
55+
> - **空間計算量**:処理中に使うメモリ量がどう増えるかの目安
56+
> - **deque(両端キュー)**:先頭・末尾どちらからでも追加・取り出しができるデータ構造
57+
58+
---
59+
60+
# 3. 選択したアルゴリズムと理由
61+
62+
- **選択したアプローチ**: **BFS + 偶数階層はそのまま / 奇数階層は reverse**
63+
64+
| 観点 | 理由 |
65+
| ----------------------- | -------------------------------------------------------------------------------------- |
66+
| 計算量 | O(n) で全ノードを一度だけ処理。反転は各階層のサイズに応じた O(k) なのでトータルは O(n) |
67+
| 型安全性 | キューの型を `TreeNode[]` で明示でき、null チェックも自然に書ける |
68+
| 可読性 | BFS の骨格はシンプルなので「偶奇で方向を切り替える」意図がコードから一目で読み取れる |
69+
| dequeを選ばなかった理由 | JavaScriptには標準の deque がなく、配列の `unshift` は O(n) コストがかかるため不利 |
70+
| DFSを選ばなかった理由 | 再帰の深さが木の高さ分スタックを消費するため、偏った木(線形に伸びた木)では危険 |
71+
72+
> 📖 **このセクションで登場した用語**
73+
>
74+
> - **BFS(幅優先探索)**:キューを使って「今いる階層を全部処理してから次の階層へ」進む方法
75+
> - **reverse**:配列の要素順を逆にするメソッド
76+
> - **unshift**:配列の先頭に要素を追加するメソッド。末尾追加(push)と違い O(n) のコストがかかる
77+
78+
---
79+
80+
# 4. 実装コード
81+
82+
> 💡 **コードの大まかな構造(骨格)**
83+
>
84+
> 1. `root``null` なら空配列を即リターン(ガード節)
85+
> 2. キュー(=行列)に `root` を入れて BFS 開始
86+
> 3. 各階層のノードを全て取り出しながら値を収集し、子ノードをキューへ追加
87+
> 4. 奇数階層(1, 3, 5…)なら収集した値を逆順にして結果に追加
88+
> 5. 全階層を処理し終えた結果配列を返す
89+
90+
```typescript
91+
function zigzagLevelOrder(root: TreeNode | null): number[][] {
92+
// ── ガード節 ────────────────────────────────────────────────
93+
// root が null(空ツリー)なら即座に空配列を返す。
94+
// 後続の処理でノードへアクセスして null 参照エラーが起きるのを防ぐため。
95+
if (root === null) return [];
96+
97+
// ── 結果格納用の配列 ─────────────────────────────────────────
98+
// 各階層の値の配列を順に格納していく。最終的にこれを返す。
99+
const result: number[][] = [];
100+
101+
// ── キュー(待ち行列)の初期化 ───────────────────────────────
102+
// キューとは「先に入れたものが先に出る(FIFO)」データ構造。
103+
// レジの行列と同じイメージで、最初にルートノードを並ばせる。
104+
const queue: TreeNode[] = [root];
105+
106+
// ── BFS メインループ ─────────────────────────────────────────
107+
// キューが空になるまで繰り返す。
108+
// 「キューが空 = 未処理のノードがなくなった」ことを意味する。
109+
while (queue.length > 0) {
110+
// 現在の階層にいるノード数を確定させる。
111+
// ループ中にキューへ子ノードを追加していくため、
112+
// 「今の階層のノード数」をループ開始時点で固定しておく必要がある。
113+
const levelSize: number = queue.length;
114+
115+
// この階層のノード値を格納する一時配列。
116+
// 後で偶奇に応じて逆順にするため、先に全値を収集する。
117+
const levelValues: number[] = [];
118+
119+
// ── 現在の階層を全て処理する ───────────────────────────────
120+
for (let i = 0; i < levelSize; i++) {
121+
// キューの先頭からノードを取り出す。
122+
// shift() は配列の先頭を取り出す操作(BFS の「先入れ先出し」を実現)。
123+
// ここで取り出すのは必ず TreeNode(null が入ることはない)
124+
// のでアサーション(!)で TypeScript に伝える。
125+
const node = queue.shift()!;
126+
127+
// 現在ノードの値を収集する。
128+
// ジグザグ処理は後でまとめて行うため、ここでは単純に追加。
129+
levelValues.push(node.val);
130+
131+
// 左の子ノードが存在すれば次の階層用にキューへ追加する。
132+
// null チェックをしてから追加することで、
133+
// null をキューに入れて後続処理がクラッシュするのを防ぐ。
134+
if (node.left !== null) queue.push(node.left);
135+
136+
// 右の子ノードも同様にキューへ追加する。
137+
if (node.right !== null) queue.push(node.right);
138+
}
139+
140+
// ── ジグザグ処理(偶奇による方向切り替え)──────────────────
141+
// result.length は「今まで完了した階層数」と等しい。
142+
// - result.length が偶数(0, 2, 4…)→ 左→右(そのまま)
143+
// - result.length が奇数(1, 3, 5…)→ 右→左(逆順)
144+
// reverse() は配列を破壊的に逆順にする。levelValues は
145+
// この後使わないので破壊的操作で問題ない。
146+
if (result.length % 2 === 1) {
147+
levelValues.reverse();
148+
}
149+
150+
// 処理済み階層の値を最終結果に追加する。
151+
result.push(levelValues);
152+
}
153+
154+
// 全階層を処理した結果を返す。
155+
return result;
156+
}
157+
```
158+
159+
---
160+
161+
# 5. 動作トレース
162+
163+
入力: `root = [3, 9, 20, null, null, 15, 7]`
164+
165+
```
166+
【ツリーの形状】
167+
3 ← 階層 0(偶数 → 左→右)
168+
/ \
169+
9 20 ← 階層 1(奇数 → 右→左)
170+
/ \
171+
15 7 ← 階層 2(偶数 → 左→右)
172+
173+
┌─────────────────────────────────────────────────────────┐
174+
│ Step 0: 初期状態 │
175+
│ queue = [Node(3)] │
176+
│ result = [] │
177+
└─────────────────────────────────────────────────────────┘
178+
179+
┌─────────────────────────────────────────────────────────┐
180+
│ Step 1: 階層 0 を処理(result.length=0 → 偶数 → そのまま)│
181+
│ levelSize = 1 │
182+
│ 取り出し : Node(3) → levelValues = [3] │
183+
│ 子を追加 : queue = [Node(9), Node(20)] │
184+
│ 偶数階層 : reverse しない → [3] │
185+
│ result = [[3]] │
186+
└─────────────────────────────────────────────────────────┘
187+
188+
┌─────────────────────────────────────────────────────────┐
189+
│ Step 2: 階層 1 を処理(result.length=1 → 奇数 → 逆順) │
190+
│ levelSize = 2 │
191+
│ 取り出し : Node(9) → levelValues = [9] │
192+
│ Node(20) → levelValues = [9, 20] │
193+
│ 子を追加 : Node(9) の子は null / Node(20) の子追加 │
194+
│ queue = [Node(15), Node(7)] │
195+
│ 奇数階層 : reverse → [20, 9] │
196+
│ result = [[3], [20, 9]] │
197+
└─────────────────────────────────────────────────────────┘
198+
199+
┌─────────────────────────────────────────────────────────┐
200+
│ Step 3: 階層 2 を処理(result.length=2 → 偶数 → そのまま)│
201+
│ levelSize = 2 │
202+
│ 取り出し : Node(15) → levelValues = [15] │
203+
│ Node(7) → levelValues = [15, 7] │
204+
│ 子を追加 : 子は全て null → queue = [] │
205+
│ 偶数階層 : reverse しない → [15, 7] │
206+
│ result = [[3], [20, 9], [15, 7]] │
207+
└─────────────────────────────────────────────────────────┘
208+
209+
✅ queue が空になったのでループ終了
210+
🎉 最終出力: [[3], [20, 9], [15, 7]]
211+
```
212+
213+
---
214+
215+
# LeetCode 提出コード(最終版)
216+
217+
```typescript
218+
// Runtime 0 ms
219+
// Beats 100.00%
220+
// Memory 58.12 MB
221+
// Beats 22.08%
222+
function zigzagLevelOrder(root: TreeNode | null): number[][] {
223+
if (root === null) return [];
224+
225+
const result: number[][] = [];
226+
const queue: TreeNode[] = [root];
227+
228+
while (queue.length > 0) {
229+
const levelSize: number = queue.length;
230+
const levelValues: number[] = [];
231+
232+
for (let i = 0; i < levelSize; i++) {
233+
const node = queue.shift()!;
234+
levelValues.push(node.val);
235+
if (node.left !== null) queue.push(node.left);
236+
if (node.right !== null) queue.push(node.right);
237+
}
238+
239+
if (result.length % 2 === 1) levelValues.reverse();
240+
result.push(levelValues);
241+
}
242+
243+
return result;
244+
}
245+
```
246+
247+
---
248+
249+
# TypeScript固有の最適化観点
250+
251+
### `shift()!` の Non-null アサーション(`!`)について
252+
253+
TypeScriptは `Array.shift()` の戻り値型を `T | undefined` と判断します。しかし今回は `levelSize = queue.length` でサイズを固定し、`levelSize` 回だけループしているため、**ループ内で `shift()``undefined` を返すことは構造上ありえません**。そのため `!` アサーション(=「私が保証するのでundefinedチェックは不要」と伝える記号)を使うことで型エラーを回避しています。
254+
255+
```typescript
256+
// TypeScript がエラーを出す(T | undefined を node: TreeNode に代入できない)
257+
const node: TreeNode = queue.shift(); //
258+
259+
// ! で「undefined にならないことを私が保証する」と伝える
260+
const node = queue.shift()!; //
261+
```
262+
263+
### なぜ `const``queue` を宣言するか
264+
265+
`const` は「変数自体の再代入禁止」であり、配列の中身の変更(`push` / `shift`)は許されます。これにより「`queue` という名前が別の配列に差し替えられる」バグを防ぎつつ、BFSの操作は問題なく行えます。
266+
267+
---
268+
269+
> 📖 **このセクションで登場した用語**
270+
>
271+
> - **BFS(幅優先探索)**:キューを使って階層ごとに探索する方法。パン屋のレジ行列のように「先に来た人が先に処理される」
272+
> - **キュー(Queue)**:先入れ先出し(FIFO)のデータ構造。`push` で末尾追加、`shift` で先頭取り出し
273+
> - **ガード節(早期リターン)**:関数の冒頭でエラーや特殊ケースをチェックし、すぐ `return` する書き方。後続のコードをシンプルに保てる
274+
> - **Non-null アサーション(`!`**:TypeScriptに「この値は絶対に null/undefined ではない」と伝える記号。使いすぎると危険なので、「構造上あり得ない」と証明できる場合のみ使う
275+
> - **reverse()**:配列を破壊的に(元の配列を直接変えて)逆順にするメソッド
276+
> - **shift()**:配列の先頭要素を取り出すメソッド。取り出した要素は配列から削除される

0 commit comments

Comments
 (0)