-
Notifications
You must be signed in to change notification settings - Fork 0
102. Binary Tree Level Order Traversal #40
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| # 102. Binary Tree Level Order Traversal | ||
| - 問題: https://leetcode.com/problems/binary-tree-level-order-traversal/ | ||
| - 言語: Python | ||
|
|
||
| ## Step1 | ||
| - 二分木の階層(レベル)ごとに節の値をグループ化して返す | ||
| ### 方針 | ||
| - 実行時間の見積: 入力長の最大は2000、計算量 $O(n)$ としてPythonでは $10^{7}$ ステップ/秒としたときに、$2000 / 10^{7} = 2 \times 10^{3} / 10^{7} = 2 \times 10^{-4}$ より、およそ0.2ms | ||
| - 階層ごとに探索するので、BFSを使う | ||
| - 手作業で考える | ||
| - 最初は情報として、根は階層1であること、階層1では節は1つ、現在の節(根)に左節、右節があるか(=次の階層の節の個数)が判明する | ||
| - 次の階層を探索するときに、階層番号、節の数、節自体を引き継ぎ情報として渡す | ||
| - 節の個数を階層ごとの探索範囲(ループ回数)とする | ||
| - ここまで考えていたら15分経過したので、正答を見る | ||
|
|
||
| ### 正答 | ||
| - 方針自体はOK | ||
| #### BFS版 | ||
| ```py | ||
| # 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: Optional[TreeNode]) -> List[List[int]]: | ||
| if root is None: | ||
| return [] | ||
|
|
||
| level_by_level = [] | ||
| pending_nodes = deque([root]) | ||
|
|
||
| while pending_nodes: | ||
| level_size = len(pending_nodes) | ||
| vals_of_level = [] | ||
|
|
||
| for _ in range(level_size): | ||
| node = pending_nodes.popleft() | ||
| vals_of_level.append(node.val) | ||
|
|
||
| if node.left: | ||
| pending_nodes.append(node.left) | ||
| if node.right: | ||
| pending_nodes.append(node.right) | ||
|
|
||
| level_by_level.append(vals_of_level) | ||
|
|
||
| return level_by_level | ||
| ``` | ||
| - BFSでは階層番号、節の数は明示的に引き継がなくても良い | ||
|
|
||
| ### 再帰DFS版 | ||
| ```py | ||
| # 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: Optional[TreeNode]) -> List[List[int]]: | ||
| result = [] | ||
|
|
||
| def build_level_vals(node, level): | ||
| if node is None: | ||
| return | ||
|
|
||
| # その階層のリストがまだなければ追加 | ||
| if level == len(result): | ||
| result.append([]) | ||
|
|
||
| # level番号で仕分け | ||
| result[level].append(node.val) | ||
|
|
||
| # 前順(pre-order)で再帰 | ||
| build_level_vals(node.left, level + 1) | ||
| build_level_vals(node.right, level + 1) | ||
|
|
||
| build_level_vals(root, 0) | ||
| return result | ||
| ``` | ||
| - DFSでは明示的に階層番号を引き継ぐ | ||
|
|
||
| ### 再帰iterative版 | ||
| ```py | ||
| # 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: Optional[TreeNode]) -> List[List[int]]: | ||
| if not root: | ||
| return [] | ||
|
|
||
| result = [] | ||
| stack = [(root, 0)] # (node, level) | ||
|
|
||
| while stack: | ||
| node, level = stack.pop() | ||
|
|
||
| if level == len(result): | ||
| result.append([]) | ||
|
|
||
| result[level].append(node.val) | ||
|
|
||
| if node.right: | ||
| stack.append((node.right, level + 1)) | ||
| if node.left: | ||
| stack.append((node.left, level + 1)) | ||
|
|
||
| return result | ||
| ``` | ||
| - DFSでは明示的に階層番号を引き継ぐ | ||
|
|
||
| ## Step2 | ||
| - 典型コメント集: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.hmkwp3k9djib | ||
|
|
||
|
|
||
| - https://github.com/akmhmgc/arai60/pull/22 | ||
| - Ruby | ||
| - Rubyの記法に慣れていないため、簡潔だがすぐに理解できなかった | ||
| - 節の個数でループを回す方法は「一つの変数に、2つの違う種類のものを入れておいて、その境界を個数で管理」する方法であり、好ましくないこともあるらしい | ||
| - cf. https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.ho7q4rvwsa1g | ||
| - flat mapで階層ごとに処理 | ||
|
|
||
| ### 個数の情報を使わずに2つのキューで管理するBFS | ||
| ```py | ||
| def level_order(root): | ||
| if root is None: | ||
| return [] | ||
|
|
||
| current_queue = deque([root]) | ||
| result = [] | ||
|
|
||
| while current_queue: | ||
| next_queue = deque() # 次の階層用 | ||
| current_level = [] | ||
|
|
||
| while current_queue: | ||
| node = current_queue.popleft() | ||
| current_level.append(node.val) | ||
| if node.left: | ||
| next_queue.append(node.left) | ||
| if node.right: | ||
| next_queue.append(node.right) | ||
|
|
||
| result.append(current_level) | ||
| current_queue = next_queue # 次の階層へ | ||
|
|
||
| return result | ||
| ``` | ||
|
|
||
| ## Step3 | ||
| ### 読みやすく書き直したコード | ||
| ```py | ||
| class Solution: | ||
| def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
| if root is None: | ||
| return [] | ||
|
|
||
| level_order = [] | ||
|
Comment on lines
+161
to
+164
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.
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. 確かにおっしゃる通りですね。 読みやすさとしては議論の余地がありますが、以下のようにするとガード節自体をなくせることに気がつきました。 pending_nodes = deque([root] if root else [])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. 個人的には、 early return を先に書いたほうが、読み手に取って読みやすくなると思います。理由は、関数の宣言と early return のあいだにコードが挟まっていると、そのコードの内容を early return のコードを読むあいだ、短期記憶にとどめておかなければならないためです。 |
||
| pending_nodes = deque([root]) | ||
|
|
||
| while pending_nodes: | ||
| current_level = [] | ||
|
|
||
| for _ in range(len(pending_nodes)): | ||
| node = pending_nodes.popleft() | ||
| current_level.append(node.val) | ||
|
|
||
| if node.left: | ||
| pending_nodes.append(node.left) | ||
| if node.right: | ||
| pending_nodes.append(node.right) | ||
|
Comment on lines
+174
to
+177
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.
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. 確かに |
||
|
|
||
| level_order.append(current_level) | ||
|
|
||
| return level_order | ||
| ``` | ||
| - 所要時間: | ||
| - 1回目: 4:49 | ||
| - 2回目: 3:06 | ||
| - 3回目: 3:08 | ||
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.
level_values values_at_level といった変数名も考えられそうです。このあたり、自分には英語のニュアンスの違いが分かりませんでした。申し訳ありません。
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.
この命名
level_by_levelに関しては、「レベルの中にレベルがある」を意図しました。意図が伝わらなさそうでしたのでStep3では改善しました。