From 94d72b969e3892d67f886036d308b050067d1af5 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 24 Aug 2025 18:06:34 +0900 Subject: [PATCH 1/4] Solve 102_binary_tree_level_order_traversal_medium --- .../README.md | 170 ++++++++++++++++++ .../bfs.py | 38 ++++ .../iterative.py | 40 +++++ .../step1.py | 42 +++++ .../step2.py | 37 ++++ 5 files changed, 327 insertions(+) create mode 100644 102_binary_tree_level_order_traversal_medium/README.md create mode 100644 102_binary_tree_level_order_traversal_medium/bfs.py create mode 100644 102_binary_tree_level_order_traversal_medium/iterative.py create mode 100644 102_binary_tree_level_order_traversal_medium/step1.py create mode 100644 102_binary_tree_level_order_traversal_medium/step2.py diff --git a/102_binary_tree_level_order_traversal_medium/README.md b/102_binary_tree_level_order_traversal_medium/README.md new file mode 100644 index 0000000..ed571d8 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/README.md @@ -0,0 +1,170 @@ +# 問題へのリンク +[Binary Tree Level Order Traversal - LeetCode](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) + + +# 言語 +Python + +# 問題の概要 + + +# 自分の解法 + +## step1 + +```python +from collections import defaultdict + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + level_to_values: dict[int, list[int]] = defaultdict(list) + + def search(node: TreeNode | None, level: int) -> None: + if not node: + return + if node.left: + search(node.left, level + 1) + if node.right: + search(node.right, level + 1) + level_to_values[level].append(node.val) + + search(root, 0) + num_levels = len(level_to_values) + level_to_values_list = [[] for _ in range(num_levels)] + for level, values in level_to_values.items(): + level_to_values_list[level] = values + return level_to_values_list +``` + + + +- ロジックはBFSと同じくらい明確 +- 実運用を見据えると、以下のような使い分けになりそう。よりスケーラブルなのはBFSか? + - 木が平衡二分木に近く、メモリ効率を重視する場合→DFS + - 木がすべてメモリに載らない場合→BFS + - 木が非常に偏っている場合→BFS + - 各レベルごとに取得したい場合→BFS + +ノードの数を`n`、木の最大高さを`H`とすると、 +- 時間計算量:`O(n)` +- 空間計算量:`O(H)` + + +## step2 DFS + +```python +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + level_to_values: list[list[int]] = [] + + def search(node: TreeNode | None, level: int) -> None: + if not node: + return + if node.left: + search(node.left, level + 1) + if node.right: + search(node.right, level + 1) + while len(level_to_values) <= level: + level_to_values.append([]) + level_to_values[level].append(node.val) + + search(root, 0) + return level_to_values +``` + +- Step1では辞書を使っていたが、はじめからリストを用いることで、最後にリストに変換する手間を省いた +- step1ではpost-order traversalを用いていたが、step2ではpre-order traversalを用いた。 + - どちらでも動くが、pre-orderの方が「訪れた順に値がリストに入る」という要件に対して、より自然に思える + +計算量:ステップ1に同じ + + +## step3 + +## step4 (FB) + + +# 別解・模範解答 +## BFS(レベルばらばら) +```python +from collections import deque + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + node_queue = deque([(root, 0)]) # (node,level) + level_to_values: list[list[int]] = [] + while node_queue: + node, level = node_queue.popleft() + while len(level_to_values) <= level: + level_to_values.append([]) + level_to_values[level].append(node.val) + if node.left: + node_queue.append((node.left, level + 1)) + if node.right: + node_queue.append((node.right, level + 1)) + + return level_to_values +``` + +## BFS(レベルごと) + +- 可読性が高く、実運用上も自然(各レベルごとにデータを取得したい場面には使いやすい) + +```python +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + level_to_values = [] + nodes = [root] + while nodes: + this_level_values = [] + child_nodes = [] + for node in nodes: + if not node: + continue + this_level_values.append(node.val) + if node.left: + child_nodes.append(node.left) + if node.right: + child_nodes.append(node.right) + level_to_values.append(this_level_values) + nodes = child_nodes + return level_to_values +``` + + +ノードの数を`n`、木の最大幅を`W`とすると、 +- 時間計算量:`O(n)` +- 空間計算量:`O(W)` + + +# 想定されるフォローアップ質問 + +## CS 基礎 +Q.「あなたが実装した反復的なBFSアプローチの空間計算量は、木のどのような特性に依存しますか?また、深さ優先探索(DFS)を用いた再帰的なアプローチと比較した場合、どのような形状の木でBFSがより多くのメモリを消費し、逆にどのような形状の木でDFSがより多くのメモリを消費するか、具体例を挙げて説明してください。」 +A. +- BFSアプローチの空間計算量は木の最大幅に依存する。 + - もし木が完全二分木なら、キューの最大サイズは`n//2=O(n)`になる。 +- DFSアプローチの空間計算量は木の最大深さに依存する。 + - 例えば、非常に偏った木(例えば、すべてのノードが右の子ノードのみを持つ場合)では、DFSの再帰スタックが`O(n)`になる可能性がある。逆に、完全二分木ではDFSの最大深さは`O(log n)`であり、BFSの方が多くのメモリを消費する。 + +## システム設計 +Q. 「このlevelOrder関数を、非常に巨大な木(例えば、数百万ノード)を扱うサービスの一部として利用するシナリオを考えます。クライアント(例: Webフロントエンド)は、一度に全レベルを表示するのではなく、'次のレベルを取得'ボタンでレベルを1つずつ要求します。このような要求に応じて効率的にデータを返すには、現在の実装をどのように変更しますか?メモリ効率を特に重視してください。」 +- 全てのレベルに対する値のリストをメモリに置くのでは無く、Pythonのジェネレーターを使って、必要なレベルの値を逐次的に生成するように変更する。 + - メモリに保持する必要があるのは各レベルの値のリストのみで、全レベルのリストを保持する必要は無いため、メモリ効率が向上する。 +- もし木のデータがデータベースに保存されている場合、各レベルのノードをクエリで取得するようにする。 + - 例えば、各ノードが親ノードのIDを持つようなテーブル構造を考える。 + - クライアントが特定のレベルを要求した際、そのレベルのノードをデータベースから直接クエリで取得する。 + - これにより、メモリ使用量を最小限に抑えつつ、必要なデータのみを効率的に取得できる。 + +## その他 + +# 次に解く問題の予告 +- [Meeting Rooms II - LeetCode](https://leetcode.com/problems/meeting-rooms-ii/) +- [Linked List Cycle II - LeetCode](https://leetcode.com/problems/linked-list-cycle-ii/) +- [Split BST - LeetCode](https://leetcode.com/problems/split-bst/) diff --git a/102_binary_tree_level_order_traversal_medium/bfs.py b/102_binary_tree_level_order_traversal_medium/bfs.py new file mode 100644 index 0000000..9195f96 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/bfs.py @@ -0,0 +1,38 @@ +# +# @lc app=leetcode id=102 lang=python3 +# +# [102] Binary Tree Level Order Traversal +# + +# @lc code=start +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + level_to_values = [] + nodes = [root] + while nodes: + this_level_values = [] + child_nodes = [] + for node in nodes: + if not node: + continue + this_level_values.append(node.val) + if node.left: + child_nodes.append(node.left) + if node.right: + child_nodes.append(node.right) + level_to_values.append(this_level_values) + nodes = child_nodes + return level_to_values + + +# @lc code=end diff --git a/102_binary_tree_level_order_traversal_medium/iterative.py b/102_binary_tree_level_order_traversal_medium/iterative.py new file mode 100644 index 0000000..c84b9b9 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/iterative.py @@ -0,0 +1,40 @@ +# +# @lc app=leetcode id=102 lang=python3 +# +# [102] Binary Tree Level Order Traversal +# + +# @lc code=start + + +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +from collections import deque + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + node_queue = deque([(root, 0)]) # (node,level) + level_to_values: list[list[int]] = [] + while node_queue: + node, level = node_queue.popleft() + while len(level_to_values) <= level: + level_to_values.append([]) + level_to_values[level].append(node.val) + if node.left: + node_queue.append((node.left, level + 1)) + if node.right: + node_queue.append((node.right, level + 1)) + + return level_to_values + + +# @lc code=end diff --git a/102_binary_tree_level_order_traversal_medium/step1.py b/102_binary_tree_level_order_traversal_medium/step1.py new file mode 100644 index 0000000..c152262 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/step1.py @@ -0,0 +1,42 @@ +# +# @lc app=leetcode id=102 lang=python3 +# +# [102] Binary Tree Level Order Traversal +# + +# @lc code=start + + +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +from collections import defaultdict + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + level_to_values: dict[int, list[int]] = defaultdict(list) + + def search(node: TreeNode | None, level: int) -> None: + if not node: + return + level_to_values[level].append(node.val) + if node.left: + search(node.left, level + 1) + if node.right: + search(node.right, level + 1) + + search(root, 0) + num_levels = len(level_to_values) + level_to_values_list = [[] for _ in range(num_levels)] + for level, values in level_to_values.items(): + level_to_values_list[level] = values + return level_to_values_list + + +# @lc code=end diff --git a/102_binary_tree_level_order_traversal_medium/step2.py b/102_binary_tree_level_order_traversal_medium/step2.py new file mode 100644 index 0000000..9ec5432 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/step2.py @@ -0,0 +1,37 @@ +# +# @lc app=leetcode id=102 lang=python3 +# +# [102] Binary Tree Level Order Traversal +# + +# @lc code=start + + +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + level_to_values: list[list[int]] = [] + + def traverse_tree(node: TreeNode | None, level: int) -> None: + if not node: + return + while len(level_to_values) <= level: + level_to_values.append([]) + level_to_values[level].append(node.val) + if node.left: + traverse_tree(node.left, level + 1) + if node.right: + traverse_tree(node.right, level + 1) + + traverse_tree(root, 0) + return level_to_values + + +# @lc code=end From fe3b81023a0b771bd0a2825d17c9f8a3ac3ad180 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 31 Aug 2025 14:30:20 +0900 Subject: [PATCH 2/4] Add step3 --- .../README.md | 21 +++++++++++ .../step3.py | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 102_binary_tree_level_order_traversal_medium/step3.py diff --git a/102_binary_tree_level_order_traversal_medium/README.md b/102_binary_tree_level_order_traversal_medium/README.md index ed571d8..6c3d2c8 100644 --- a/102_binary_tree_level_order_traversal_medium/README.md +++ b/102_binary_tree_level_order_traversal_medium/README.md @@ -81,6 +81,27 @@ class Solution: ## step3 +```python +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + + nodes = [root] + level_order_node_vals = [] + while nodes: + node_vals = [] + next_level_nodes = [] + for node in nodes: + node_vals.append(node.val) + if node.left: + next_level_nodes.append(node.left) + if node.right: + next_level_nodes.append(node.right) + level_order_node_vals.append(node_vals) + nodes = next_level_nodes + return level_order_node_vals +``` ## step4 (FB) diff --git a/102_binary_tree_level_order_traversal_medium/step3.py b/102_binary_tree_level_order_traversal_medium/step3.py new file mode 100644 index 0000000..85d91e6 --- /dev/null +++ b/102_binary_tree_level_order_traversal_medium/step3.py @@ -0,0 +1,37 @@ +# +# @lc app=leetcode id=102 lang=python3 +# +# [102] Binary Tree Level Order Traversal +# + +# @lc code=start +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +class Solution: + def levelOrder(self, root: TreeNode | None) -> list[list[int]]: + if not root: + return [] + + nodes = [root] + level_order_node_vals = [] + while nodes: + node_vals = [] + next_level_nodes = [] + for node in nodes: + node_vals.append(node.val) + if node.left: + next_level_nodes.append(node.left) + if node.right: + next_level_nodes.append(node.right) + level_order_node_vals.append(node_vals) + nodes = next_level_nodes + return level_order_node_vals + + +# @lc code=end From a879f8e7a2af8b38b615bfdfa1fc4e85098ba5c1 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 31 Aug 2025 14:32:15 +0900 Subject: [PATCH 3/4] Update README --- 102_binary_tree_level_order_traversal_medium/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/102_binary_tree_level_order_traversal_medium/README.md b/102_binary_tree_level_order_traversal_medium/README.md index 6c3d2c8..2a3f282 100644 --- a/102_binary_tree_level_order_traversal_medium/README.md +++ b/102_binary_tree_level_order_traversal_medium/README.md @@ -6,6 +6,7 @@ Python # 問題の概要 +与えられた二分木の各レベルにおけるノードの値をリストとして返す問題。 # 自分の解法 From 01d24ce5186dfdd20f5d0790a158998ce13b91d4 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 1 Sep 2025 09:42:44 +0900 Subject: [PATCH 4/4] Update README --- .../README.md | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/102_binary_tree_level_order_traversal_medium/README.md b/102_binary_tree_level_order_traversal_medium/README.md index 2a3f282..ff17adf 100644 --- a/102_binary_tree_level_order_traversal_medium/README.md +++ b/102_binary_tree_level_order_traversal_medium/README.md @@ -24,11 +24,11 @@ class Solution: def search(node: TreeNode | None, level: int) -> None: if not node: return + level_to_values[level].append(node.val) if node.left: search(node.left, level + 1) if node.right: search(node.right, level + 1) - level_to_values[level].append(node.val) search(root, 0) num_levels = len(level_to_values) @@ -38,8 +38,6 @@ class Solution: return level_to_values_list ``` - - - ロジックはBFSと同じくらい明確 - 実運用を見据えると、以下のような使い分けになりそう。よりスケーラブルなのはBFSか? - 木が平衡二分木に近く、メモリ効率を重視する場合→DFS @@ -59,24 +57,24 @@ class Solution: def levelOrder(self, root: TreeNode | None) -> list[list[int]]: level_to_values: list[list[int]] = [] - def search(node: TreeNode | None, level: int) -> None: + def traverse_tree(node: TreeNode | None, level: int) -> None: if not node: return - if node.left: - search(node.left, level + 1) - if node.right: - search(node.right, level + 1) while len(level_to_values) <= level: level_to_values.append([]) level_to_values[level].append(node.val) + if node.left: + traverse_tree(node.left, level + 1) + if node.right: + traverse_tree(node.right, level + 1) - search(root, 0) + traverse_tree(root, 0) return level_to_values ``` - Step1では辞書を使っていたが、はじめからリストを用いることで、最後にリストに変換する手間を省いた -- step1ではpost-order traversalを用いていたが、step2ではpre-order traversalを用いた。 - - どちらでも動くが、pre-orderの方が「訪れた順に値がリストに入る」という要件に対して、より自然に思える +- step1、step2ではpre-order traversalを用いた。 + - post-orderでも動くが、pre-orderの方が「訪れた順に値がリストに入る」という要件に対して、より自然に思える 計算量:ステップ1に同じ @@ -87,7 +85,6 @@ class Solution: def levelOrder(self, root: TreeNode | None) -> list[list[int]]: if not root: return [] - nodes = [root] level_order_node_vals = [] while nodes: