diff --git a/DataStructures/Trees/Other/101. Symmetric Tree/claude sonnet 4.6 extended/README.md b/DataStructures/Trees/Other/101. Symmetric Tree/claude sonnet 4.6 extended/README.md new file mode 100644 index 00000000..cc1e74fe --- /dev/null +++ b/DataStructures/Trees/Other/101. Symmetric Tree/claude sonnet 4.6 extended/README.md @@ -0,0 +1,756 @@ +# Symmetric Tree - 二分木が鏡写しかどうかを判定する + +--- + +## 目次(Table of Contents) + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +
+ 再帰 DFS + 反復 BFS による O(n) 実装 +
+ + ++ 💡 + この問題を一言で言うと:「二分木の左右が完全に鏡写しかどうかを確認する問題」 +
++ 「鏡写し」とは、木の中心軸(ルート)を境に、左サブツリーを裏返すと右サブツリーとぴったり重なる状態です。 + 単に「左右の値が同じ」だけでは不十分で、構造(形)と値の両方が対応していなければなりません。 +
++ ⚠️ なぜ単純な方法では解けないのか +
+None(存在しない)の場合のケースを3パターン(両方None・片方None・両方あり)に分けて処理しないとバグになる
+ [2,2,2,null,2])で誤検知しやすい
+ +入力: [1, 2, 2, 3, 4, 4, 3] +出力: True + + 1 + / \ + 2 2 + / \ / \ + 3 4 4 3 + +理由: 左右が完全に鏡写し + 左の左子(3) ↔ 右の右子(3) ✅ + 左の右子(4) ↔ 右の左子(4) ✅+
+入力: [1, 2, 2, null, 3, null, 3] +出力: False + + 1 + / \ + 2 2 + \ \ + 3 3 + +理由: left.leftがnullである一方で + right.rightは3なので + nullと3が一致せず非対称になる ❌+
+ 各ステップをクリックすると詳細が表示されます。▶ Play で自動再生も可能です。 +
+ ++ 📋 このコードの構造(先に全体像を把握しよう) +
+isSymmetric:エントリーポイント。root が None なら即
+ True、そうでなければヘルパーへ
+ _is_mirror:2つのノードを受け取り、3ケース(両方None・片方None・両方あり)で鏡写し判定
+ isSymmetricIterative:deque を使う反復版(スタック深度制限を回避したい場合)
+ from collections import deque
+
+
+# LeetCode が提供する TreeNode クラス(提出時はコメント済みのものを使用)
+# class TreeNode(object):
+# def __init__(self, val=0, left=None, right=None):
+# self.val = val
+# self.left = left
+# self.right = right
+
+class Solution(object):
+
+ # =========================================================
+ # 解法①: 再帰版(メイン)
+ # =========================================================
+ def isSymmetric(self, root):
+ """
+ 二分木が鏡写し(対称)かどうかを再帰で判定する。
+
+ :type root: Optional[TreeNode]
+ :rtype: bool
+
+ Time: O(n) - 全ノードを1回ずつ訪問する
+ Space: O(h) - 再帰スタックの深さ(h = 木の高さ)
+ """
+ # rootがNone(空の木)は「対称」と定義する
+ if root is None:
+ return True
+
+ # rootは中心軸なので比較しない。左子と右子を最初のペアとして渡す
+ return self._is_mirror(root.left, root.right)
+
+ def _is_mirror(self, left, right):
+ """
+ 2つのノードが「鏡写し」の関係かどうかを再帰的に確認するヘルパー。
+
+ :type left: Optional[TreeNode]
+ :type right: Optional[TreeNode]
+ :rtype: bool
+ """
+ # ── 基底条件① ──
+ # 両方Noneなら「空同士」= 対称 → True
+ # 例: 葉ノードの子(存在しない位置)同士を比較した場合
+ if left is None and right is None:
+ return True
+
+ # ── 基底条件② ──
+ # 片方だけNoneなら「一方だけ枝がある」= 非対称 → False
+ # `is None` を使う: `== None` より高速(同一性チェック)
+ if left is None or right is None:
+ return False
+
+ # ── 再帰ステップ ──
+ # 3条件を `and` で繋ぐ。短絡評価で値が違えば即Falseを返す
+ return (
+ left.val == right.val # 条件1: 値が同じか?
+ and self._is_mirror(left.left, right.right) # 条件2: 外側ペア
+ and self._is_mirror(left.right, right.left) # 条件3: 内側ペア
+ )
+
+ # =========================================================
+ # 解法②: 反復版(フォローアップ)
+ # =========================================================
+ def isSymmetricIterative(self, root):
+ """
+ 二分木が鏡写しかどうかを反復(deque)で判定する。
+ RecursionError が心配な場合はこちらを使う。
+
+ deque を使う理由: list.pop(0) は O(n) だが
+ deque.popleft() は O(1) で高速。
+
+ :type root: Optional[TreeNode]
+ :rtype: bool
+
+ Time: O(n) Space: O(w) - wは木の最大幅
+ """
+ if root is None:
+ return True
+
+ # dequeに「鏡ペア」をタプルで格納して順番に比較する
+ queue = deque()
+ queue.append((root.left, root.right))
+
+ while queue:
+ # FIFO(先入れ先出し)でペアを取り出す
+ left, right = queue.popleft()
+
+ if left is None and right is None:
+ continue # 両方None → OK、次のペアへ
+ if left is None or right is None:
+ return False # 片方だけNone → 非対称
+ if left.val != right.val:
+ return False # 値が違う → 非対称
+
+ # 次に確認すべき鏡ペアをキューに追加
+ queue.append((left.left, right.right)) # 外側ペア
+ queue.append((left.right, right.left)) # 内側ペア
+
+ return True
+
+
+
+ ▶ 入力例
+ root = [1, 2, 2, 3, 4, 4, 3]
+ での動作トレース(再帰版)
+
+isSymmetric(root=1) + → root != None → _is_mirror(root.left=2, root.right=2) + +_is_mirror(left=Node(2), right=Node(2)) + → 両方Noneでない、片方Noneでない + → 2 == 2 ✅ + → _is_mirror(left.left=Node(3), right.right=Node(3)) ← 外側ペア + + _is_mirror(left=Node(3), right=Node(3)) + → 3 == 3 ✅ + → _is_mirror(None, None) → True ✅ + → _is_mirror(None, None) → True ✅ + → True ✅ + + → _is_mirror(left.right=Node(4), right.left=Node(4)) ← 内側ペア + + _is_mirror(left=Node(4), right=Node(4)) + → 4 == 4 ✅ + → _is_mirror(None, None) → True ✅ + → _is_mirror(None, None) → True ✅ + → True ✅ + +最終結果: True and True and True = True ✅+
+ ▶ 入力例
+ root = [1, 2, 2, null, 3, null, 3]
+ での動作トレース(短絡評価の効果)
+
+_is_mirror(left=Node(2), right=Node(2)) + → 2 == 2 ✅ + → _is_mirror(left.left=None, right.right=Node(3)) ← 外側ペア + + _is_mirror(left=None, right=Node(3)) + → 片方だけ None → False ❌ ← ここで即終了! + + → False なので and の短絡評価が働き + 内側ペアの _is_mirror は呼ばれない(省エネ!) + +最終結果: False ❌+
+ 🗺️ フローチャートの読み方 +
+
+ この図は
+ _is_mirror(left, right)
+ ヘルパー関数の処理の流れを表しています。
+ 上から下へ読み進め、ひし形の分岐で「はい/いいえ」のどちらかの経路を進みます。
+
+ 🔎 入力例
+ [1, 2, 2, 3, 4, 4, 3]
+ でのフロー追跡
+
+ 📖 Big-O 記法の読み方(入力サイズ n + が大きくなるにつれて処理時間がどう増えるかの目安) +
+| + 解法 + | ++ 時間計算量 + | ++ 空間計算量 + | ++ 最悪ケース(空間) + | +
|---|---|---|---|
| + 再帰(DFS) + | ++ O(n) + | ++ O(h) + | ++ O(n)(一直線の木) + | +
| + 反復(BFS with deque) + | ++ O(n) + | ++ O(w) + | ++ O(n)(完全二分木最下段) + | +
+ 🔍 なぜこの計算量になるのか +
+
+ 時間計算量 O(n):どちらの解法も、各ノードを最大1回ずつ訪問します。n個のノードがある木では、最大
+ n/2 ペアを比較するため O(n/2) = O(n) です。
+ 空間計算量(再帰)O(h):h
+ は木の高さです。再帰呼び出しは「コールスタック(=関数の呼び出し履歴を記録するメモリ領域)」に積み重なります。最悪ケースの一直線の木では
+ h = n になるため O(n) です。バランスの取れた木では h = log n になります。
+ 空間計算量(反復)O(w):w は木の最大幅です。deque
+ には同じ深さのペアが格納されるため、完全二分木の最下段(= n/2
+ 個のノード)が最悪ケースで O(n) になります。
+
+ ⚡ 再帰 vs 反復:どちらを選ぶか +
+🌀 再帰版を選ぶ場面
+🔁 反復版を選ぶ場面
++ このページで登場した専門用語をまとめました。分からない言葉が出てきたときに参照してください。 +
+popleft()
+ がO(1)で高速(list.pop(0)
+ はO(n))。 C言語で実装されているため Pure Python より大幅に高速。
+ sys.getrecursionlimit())。
+ A and B
+ でAが
+ False
+ なら、Bをまったく評価せず即座に
+ False
+ を返す仕組み。
+ この問題では値が違えば外側・内側ペアの再帰呼び出しが省略される。
+ 非対称が早い段階で分かるほど効果が大きい最適化テクニック。
+ TreeNode
+ クラスで表現され、.val, .left,
+ .right
+ の3つの属性を持つ。
+ append()
+ で末尾追加、popleft()
+ で先頭取り出しにより実現する。
+ sys.setrecursionlimit(n)
+ で上限を変更可能。または反復版を使うことで根本的に回避できる。
+