-
Notifications
You must be signed in to change notification settings - Fork 0
102 binary tree level order traversal medium #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Kaichi-Irie
wants to merge
4
commits into
main
Choose a base branch
from
102_binary_tree_level_order_traversal_medium
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| # 問題へのリンク | ||
| [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 | ||
| 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 | ||
| ``` | ||
|
|
||
| - ロジックは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 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 | ||
| ``` | ||
|
|
||
| - Step1では辞書を使っていたが、はじめからリストを用いることで、最後にリストに変換する手間を省いた | ||
| - step1、step2ではpre-order traversalを用いた。 | ||
| - post-orderでも動くが、pre-orderの方が「訪れた順に値がリストに入る」という要件に対して、より自然に思える | ||
|
|
||
| 計算量:ステップ1に同じ | ||
|
|
||
|
|
||
| ## 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) | ||
|
|
||
|
|
||
| # 別解・模範解答 | ||
| ## 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/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 読みやすいです。個人的にはlevel_order_node_valsはresultぐらいでもいいと思いました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューありがとうございます。 |
||
|
|
||
|
|
||
| # @lc code=end | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これは、前提として、巨大な木はサーバーサイドにあるんですよね。
そうすると、「次のレベルを取得」を押したときには、前のプロセスはもう終了していることが多いだろうと思います。そうすると必要なレベルのデータを毎回生成することになるかと思います。完全二分木だとすると、あるレベルの数はその上の数の総和よりも多いですから、(保持よりも再生成のコストのほうが安く)あまり問題にならないでしょうね。