中順巡回反復による O(n) 実装
二分木の根ノードが与えられたとき、それが有効なBST(Binary Search Tree)であるかを判定します。
BST の定義:
Example 1:
Input: root = [2,1,3]
2
/ \
1 3
Output: true
Example 2:
Input: root = [5,1,4,null,null,3,6]
5
/ \
1 4
/ \
3 6
Output: false (4は5より小さいべき)
1 ≤ n ≤ 10^4
-2^31 ≤ Node.val ≤ 2^31 - 1
核心アイデア:
BSTの中順巡回(Inorder Traversal)は厳密単調増加列を生成します(必要十分条件)。
スタックを使った反復的な巡回で全ノードを訪問し、prev(直前の値)と比較して
node.val > prev
を確認します。
prevのみで比較
from typing import Optional, List
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
"""
反復的な中順巡回でBSTを検証
時間計算量: O(n)
空間計算量: O(h) where h is tree height
"""
stack: List[TreeNode] = []
cur: Optional[TreeNode] = root
prev: Optional[int] = None
# ローカル束縛による最適化
push = stack.append
pop = stack.pop
while cur is not None or stack:
# 左部分木を全てスタックに積む
while cur is not None:
push(cur)
cur = cur.left
# 最左端のノードを取り出す
node = pop()
val = node.val
# 厳密単調増加チェック
if prev is not None and val <= prev:
return False
# prev を更新
prev = val
# 右部分木へ移動
cur = node.right
return True
フローの説明:
1. スタック、cur、prevを初期化
2.
緑の矢印(はい):curがNoneでないかスタックに要素があればループ継続
3.
左部分木探索:curがNoneでない限りスタックに積み、左へ移動
4.
赤の矢印(いいえ):最左端到達後、ノードを取り出す
5. BST違反チェック:val ≤ prev
なら即座にFalse返却
6.
紫の矢印(ループバック):prevを更新後、右部分木へ移動してループ再開
7. 全ノード通過でTrueを返却
| 項目 | 計算量 | 説明 |
|---|---|---|
| 時間計算量 |
O(n)
|
全ノードを1回ずつ訪問。各ノードでの処理はO(1) |
| 空間計算量 |
O(h)
|
スタックの最大深さは木の高さh。最悪(一本道)でO(n)、平衡木でO(log n) |
| 追加メモリ |
O(1)
|
prev変数のみ(整数1つ) |
| 手法 | 時間 | 空間 | 備考 |
|---|---|---|---|
| 本実装(Inorder反復) | O(n) | O(h) | ✓ 最適。メモリ効率が高い |
| Inorder配列化 | O(n) | O(n) | 全要素を配列に格納。無駄が多い |
| 再帰DFS(範囲チェック) | O(n) | O(h) | Pythonでは深い木で再帰限界リスク |
| Morris Traversal | O(n) | O(1) | 木を一時改変。業務では避けがち |
最適化のポイント:
prevのみで比較
push = stack.append)でCPython最適化