From df5fd6ae4262907f7cfbe016f2db7f09bd2488c4 Mon Sep 17 00:00:00 2001 From: skypenguins Date: Mon, 21 Jul 2025 00:31:47 +0900 Subject: [PATCH 1/2] 104. Maximum Depth of Binary Tree --- leetcode/arai60/memo.md | 161 ++++++++++++++ leetcode/arai60/stack-visualization.svg | 280 ++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 leetcode/arai60/memo.md create mode 100644 leetcode/arai60/stack-visualization.svg diff --git a/leetcode/arai60/memo.md b/leetcode/arai60/memo.md new file mode 100644 index 0000000..0e18c4b --- /dev/null +++ b/leetcode/arai60/memo.md @@ -0,0 +1,161 @@ +# 104. Maximum Depth of Binary Tree +問題: https://leetcode.com/problems/maximum-depth-of-binary-tree/ +言語: Python + +# Step1 +* 二分木の最大の深さ(高さ)を求める +* 幅優先探索(BFS)で深さを記録していく + +## すべてのテストケースを通過しないコード +```py +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if root is None: + return 0 + + node = root + visited_nodes = deque([node]) + candidate_tree_depth = [] + tree_depth = 1 + + while visited_nodes: + node = visited_nodes.popleft() + if node.left: + visited_nodes.append(node.left) + tree_depth += 1 + if node.right: + visited_nodes.append(node.right) + tree_depth += 1 + if not node.left and not node.right: + candidate_tree_depth.append(tree_depth) + tree_depth = 1 + + return max(candidate_tree_depth) +``` +* 完全二分木の場合はうまく動くが、そうでない場合は誤答を返す +* 40分ほど経っていたので正答を見る + +## 正答 +```py +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + + visited_nodes = deque([root]) + tree_depth = 0 + + while visited_nodes: + tree_depth += 1 + + for _ in range(len(visited_nodes)): + node = visited_nodes.popleft() + if node.left: + visited_nodes.append(node.left) + if node.right: + visited_nodes.append(node.right) + + return tree_depth +``` +* 同じくBFSを使った方法 +* 木のそれぞれの高さ(level)ごとに、その次の同じ高さにあるノードを追加していく + +# Step2 +## 他人のコードを読む +* 典型コメント: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.27jfjzhov3la +* https://github.com/sakupan102/arai60-practice/pull/21 + - Python + - 深さ優先探索(DFS)を使った方法 + - 再帰によるDFS・下から走査するボトムアップ方式 + ```py + class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + right_depth = self.maxDepth(root.right) + left_depth = self.maxDepth(root.left) + return max(right_depth, left_depth) + 1 + ``` + - スタックによるDFS・上から配るトップダウン方式 + ```py + class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + node_and_depth = [(root, 1)] + max_depth = 1 + while node_and_depth: + node, depth = node_and_depth.pop() + max_depth = max(max_depth, depth) + if node.right: + node_and_depth.append((node.right, depth + 1)) + if node.left: + node_and_depth.append((node.left, depth + 1)) + return max_depth + ``` + - 再帰によるDFS・上から配るトップダウン方式 + ```py + class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + max_depth = 0 + def dive_into_leaf(node, depth): + nonlocal max_depth + if not node: + return + max_depth = max(max_depth, depth) + dive_into_leaf(node.left, depth + 1) + dive_into_leaf(node.right, depth + 1) + dive_into_leaf(root, 1) + return max_depth + ``` + - スタックによるDFS・下から走査するボトムアップ方式 + ```py + class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + node_info = [("go", (root, None, [None], [None], [None]))] # (node, direction, depth, left_depth, right_depth) + while node_info: + go_back_flag, args = node_info.pop() + if go_back_flag == "go": + node, direction, depth_ref, left_depth_ref, right_depth_ref = args + if not node: + depth_ref[0] = 0 + continue + node_info.append(("back", args)) + node_info.append(("go", (node.left, "left", left_depth_ref,[None], [None]))) + node_info.append(("go", (node.right, "right", right_depth_ref, [None], [None]))) + if go_back_flag == "back": + node, direction, depth_ref, left_depth_ref, right_depth_ref = args + depth_ref[0] = max(left_depth_ref[0], right_depth_ref[0]) + 1 + if not direction: + return depth_ref[0] + ``` + ![](stack-visualization.svg) + - Claude 4 Opusで作成 + +# Step3 +* スタックによるDFS・上から配るトップダウン方式 +```py +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + + node_and_depth = [(root, 1)] + max_depth = 0 + + while node_and_depth: + node, depth = node_and_depth.pop() + max_depth = max(max_depth, depth) + if node.right: + node_and_depth.append((node.right, depth + 1)) + if node.left: + node_and_depth.append((node.left, depth + 1)) + + return max_depth +``` +* 解答時間 + - 1回目: 1:45 + - 2回目: 2:08 + - 3回目: 2:17 diff --git a/leetcode/arai60/stack-visualization.svg b/leetcode/arai60/stack-visualization.svg new file mode 100644 index 0000000..e9bb2bc --- /dev/null +++ b/leetcode/arai60/stack-visualization.svg @@ -0,0 +1,280 @@ + + + + + + + 二分木最大深度計算のスタック状態遷移(完全版) + + + + "go" フレーム(ノード訪問前) + + + "back" フレーム(子ノード処理後) + + ← スタックトップ(次にpopされる要素) + + + + 対象の二分木 + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + + Step 1: 初期状態 + + ["go", (node:1, direction:None, depth:[None], left:[None], right:[None])] + + TOP + + + + + Step 2: node:1を処理("go"をpop) + + ["go", (node:3, direction:"right", depth:[None], left:[None], right:[None])] + + + ["go", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[None])] + + TOP + + + + + Step 3: node:3を処理("go"をpop) + + ["go", (node:None, direction:"right", depth:[None], left:[None], right:[None])] + + + ["go", (node:None, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:3, direction:"right", depth:[None], left:[None], right:[None])] + + + ["go", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[None])] + + TOP + + + + + Step 4: node:Noneを処理(depth=0を設定) + + ["go", (node:None, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:3, direction:"right", depth:[None], left:[None], right:[0])] + ← right_depth = 0 + + + ["go", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[None])] + + TOP + + + + + Step 5: node:Noneを処理(depth=0を設定) + + ["back", (node:3, direction:"right", depth:[None], left:[0], right:[0])] + ← left_depth = 0 + + + ["go", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[None])] + + TOP + + + + + Step 6: node:3のback処理(depth = max(0,0)+1 = 1) + + ["go", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + ← right_depth = 1(node:3の深度) + + TOP + + + + + Step 7: node:2を処理("go"をpop) + + ["go", (node:None, direction:"right", depth:[None], left:[None], right:[None])] + + + ["go", (node:4, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:2, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 8: node:Noneを処理(depth=0を設定) + + ["go", (node:4, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:2, direction:"left", depth:[None], left:[None], right:[0])] + ← right_depth = 0 + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 9: node:4を処理("go"をpop) + + ["go", (node:None, direction:"right", depth:[None], left:[None], right:[None])] + + + ["go", (node:None, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:4, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:2, direction:"left", depth:[None], left:[None], right:[0])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 10: node:Noneを処理(depth=0を設定) + + ["go", (node:None, direction:"left", depth:[None], left:[None], right:[None])] + + + ["back", (node:4, direction:"left", depth:[None], left:[None], right:[0])] + ← right_depth = 0 + + + ["back", (node:2, direction:"left", depth:[None], left:[None], right:[0])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 11: node:Noneを処理(depth=0を設定) + + ["back", (node:4, direction:"left", depth:[None], left:[0], right:[0])] + ← left_depth = 0 + + + ["back", (node:2, direction:"left", depth:[None], left:[None], right:[0])] + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 12: node:4のback処理(depth = max(0,0)+1 = 1) + + ["back", (node:2, direction:"left", depth:[None], left:[1], right:[0])] + ← left_depth = 1(node:4の深度) + + + ["back", (node:1, direction:None, depth:[None], left:[None], right:[1])] + + TOP + + + + + Step 13: node:2のback処理(depth = max(1,0)+1 = 2) + + ["back", (node:1, direction:None, depth:[None], left:[2], right:[1])] + ← left_depth = 2(node:2の深度) + + TOP + + + + + Step 14: node:1のback処理(depth = max(2,1)+1 = 3) + スタックは空になる + node:1(ルート)の深度 = max(2, 1) + 1 = 3 + directionがNoneなので、3を返して終了 + + + 最大深度: 3 + + + + + 処理の流れのまとめ: + 1. 各ノードで "back" → 左の子の "go" → 右の子の "go" の順でpush + 2. LIFOなので、右→左→自身(back)の順で処理される + 3. Noneノードは深度0を設定して即座に終了 + 4. "back"処理で子の深度から自身の深度を計算 + 5. リスト参照により、親が子の結果を取得可能 + + 各ノードの計算結果: + • node:4 → 深度1(葉ノード) + • node:3 → 深度1(葉ノード) + • node:2 → 深度2(左の子:1 + 1) + • node:1 → 深度3(左の子:2 + 1) + + \ No newline at end of file From f32fc7c9487186954f8c3686aef2bec51b1e4658 Mon Sep 17 00:00:00 2001 From: Aozora Penguin Date: Mon, 21 Jul 2025 00:38:46 +0900 Subject: [PATCH 2/2] Update memo.md fix typo --- leetcode/arai60/memo.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leetcode/arai60/memo.md b/leetcode/arai60/memo.md index 0e18c4b..c6fd612 100644 --- a/leetcode/arai60/memo.md +++ b/leetcode/arai60/memo.md @@ -1,6 +1,6 @@ # 104. Maximum Depth of Binary Tree -問題: https://leetcode.com/problems/maximum-depth-of-binary-tree/ -言語: Python +* 問題: https://leetcode.com/problems/maximum-depth-of-binary-tree/ +* 言語: Python # Step1 * 二分木の最大の深さ(高さ)を求める