反復スタック(明示スタック)による O(N) 中順走査
二分木の根ノード
root が与えられる。
中順走査(左→根→右)
でノードの値を収集し、リストとして返す。
Follow-up:
再帰を使わない反復解を実装せよ。
Input: root = [1, null, 2, 3]
Output: [1, 3, 2]
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
フローの説明:
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が共に空になったら結果を返す。
全ノードをスタックに push 1回・pop 1回の合計 2N 操作。定数倍を無視すると O(N)。
明示スタックの最大深さ。最悪ケースは N ノードが全て左に偏った木(スタック深さ = N)。
| アプローチ | 時間 | 空間 | 可読性 | 安全性 |
|---|---|---|---|---|
| ✅ 反復(明示スタック) | O(N) | O(N) | ★★★ | ◎ |
| 再帰 DFS | O(N) | O(N) | ★★★ | △ ※ |
| Morris Traversal | O(N) | O(1) | ★☆☆ | △ 副作用 |
※ Python デフォルト再帰上限 1,000 でスタックオーバーフローリスクあり