-
Notifications
You must be signed in to change notification settings - Fork 0
Add 103. Binary Tree Zigzag Level Order Traversal.md #27
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,229 @@ | ||
# step 1 | ||
階層型BFSが穏当そう。 | ||
|
||
DFSは流石に無理かと感じた。あるlevelで初めに一番左のノードから見て、その次のlevelでは | ||
逆方向というのをうまくやる方法が思いつかなかった。 | ||
|
||
階層型でないBFSについても、ノードを入れる/取り出す順序を変える必要を感じた。 | ||
|
||
`nodes_in_level`, `nodes_in_next_level`ではあるレベルのノードが | ||
左から右に並ぶように統一して解いた。 | ||
|
||
- time complexity: O(n) | ||
- space complexity: O(n) | ||
```python | ||
from collections import deque | ||
|
||
|
||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
zigzag_traversed_values = [] | ||
nodes_in_level = deque([root]) | ||
start_from_left = True | ||
while True: | ||
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. 個人的にループ条件を指定できるのであればしたいなという気持ちになりました。仮にもっと長いソースで何か変更して無限ループになった時、ソースを読む負担が減るんじゃないかなと思いました。 |
||
nodes_in_level = deque(filter(None, nodes_in_level)) | ||
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. None を取り除いているということが分かりにくく感じました。 |
||
if not nodes_in_level: | ||
break | ||
values_in_level = [] | ||
nodes_in_next_level = deque() | ||
while nodes_in_level: | ||
if start_from_left: | ||
node = nodes_in_level.popleft() | ||
values_in_level.append(node.val) | ||
nodes_in_next_level.append(node.left) | ||
nodes_in_next_level.append(node.right) | ||
else: | ||
node = nodes_in_level.pop() | ||
values_in_level.append(node.val) | ||
nodes_in_next_level.appendleft(node.right) | ||
nodes_in_next_level.appendleft(node.left) | ||
Comment on lines
+36
to
+39
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. 個人的には、本当に逆順で入っているかの確認のためにL29から32とL36から39をじっくり見比べる必要があり、あまり好みではないです。(depthが奇数の時reverseする、などの処理だけの方が読む負担が少ない) |
||
zigzag_traversed_values.append(values_in_level) | ||
nodes_in_level = nodes_in_next_level | ||
start_from_left = not start_from_left | ||
return zigzag_traversed_values | ||
``` | ||
|
||
ループの先頭ではstart_from_leftがTrueの時はnodes_in_levelが右のノードから左のノードに、 | ||
Falseの時はnodes_in_levelが左から右に並ぶようにした解法。(dequeを使わない解法) | ||
|
||
個人的にはあまり好みではない。 | ||
```python | ||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
zigzag_traversed_values = [] | ||
start_from_left = True | ||
nodes_in_level = [root] | ||
while True: | ||
nodes_in_level = list(filter(None, nodes_in_level)) | ||
if not nodes_in_level: | ||
break | ||
nodes_in_level.reverse() | ||
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. これも結構読む時の認知負荷がありますね。 |
||
nodes_in_next_level = [] | ||
values_in_level = [] | ||
for node in nodes_in_level: | ||
values_in_level.append(node.val) | ||
if start_from_left: | ||
nodes_in_next_level.append(node.left) | ||
nodes_in_next_level.append(node.right) | ||
else: | ||
nodes_in_next_level.append(node.right) | ||
nodes_in_next_level.append(node.left) | ||
nodes_in_level = nodes_in_next_level | ||
start_from_left = not start_from_left | ||
zigzag_traversed_values.append(values_in_level) | ||
return zigzag_traversed_values | ||
``` | ||
|
||
Noneもとりあえず入れる方法にしたが、弾くようにするとnodes_in_next_level.append()でネストが | ||
深くなる。 | ||
# step 2 | ||
- https://github.com/olsen-blue/Arai60/pull/27/files | ||
- 階層型BFS | ||
- ノードの探索は通常のBFSと同じにしており、あるレベルでの値のリストを必要に応じて反転している | ||
- https://github.com/colorbox/leetcode/pull/41/files | ||
- ほぼ上と同じ | ||
- https://github.com/goto-untrapped/Arai60/pull/51#discussion_r1749461242 | ||
- リストに入れる時にひっくり返すか、最後にまとめてひっくり返すか | ||
- >自分も入れるときひっくり返す方が好きですが、最後に奇数番目の列をひっくり返すのもありかなと思います | ||
それぞれのloopでやることを1つに絞ってるので初見での読みやすさが上がってそうです | ||
- https://github.com/goto-untrapped/Arai60/pull/51/files | ||
- DFSによる解法あり。読みやすいかは別にして面白かった。思いつかなかったのが悔しい。 | ||
- traverseの順番に規則があるのなら、それと今の深さの兼ね合いで返り値となるリストの作り方をうまいこと調整できる。 | ||
- https://github.com/python/cpython/blob/main/Objects/listobject.c#L1527 | ||
- pythonのlistのreverseメソッド | ||
- 中でreverse_slice()を呼んでいてこれ自体はO(n) | ||
- https://docs.python.org/3/library/functions.html#reversed | ||
- あれば`__reversed__()`という特殊メソッドを読んでいるみたい | ||
- https://github.com/python/cpython/blob/main/Objects/listobject.c#L4071 | ||
- https://github.com/python/cpython/blob/main/Objects/listobject.c#L4103 | ||
- listの`__reversed__()`は末尾のイテレータを返しているだけみたい。 | ||
- traverseで必要になったら、後ろから調べるようにしている。 | ||
|
||
step 1の解法ではノードもちゃんとzigzagに進むように作っていたが、必ずしもそうする必要はない。 | ||
そうしなかった時の方が複雑さが少ないように感じた。 | ||
|
||
通常のtraverseをしてから、リストをひっくり返していく | ||
```python | ||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
level_ordered_values = [] | ||
nodes_in_level = [root] | ||
while True: | ||
nodes_in_level = list(filter(None, nodes_in_level)) | ||
if not nodes_in_level: | ||
break | ||
nodes_in_next_level = [] | ||
values_in_level = [] | ||
for node in nodes_in_level: | ||
values_in_level.append(node.val) | ||
nodes_in_next_level.append(node.left) | ||
nodes_in_next_level.append(node.right) | ||
nodes_in_level = nodes_in_next_level | ||
level_ordered_values.append(values_in_level) | ||
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. ここで if not start_from_left: values_in_level.reverse() とすると下がいらなくなりますね。 |
||
|
||
start_from_left = True | ||
zigzag_traversed_values = [] | ||
for left_to_right_traversed_values in level_ordered_values: | ||
if start_from_left: | ||
zigzag_traversed_values.append(left_to_right_traversed_values) | ||
else: | ||
right_to_left_traversed_values = list( | ||
reversed(left_to_right_traversed_values) | ||
) | ||
zigzag_traversed_values.append(right_to_left_traversed_values) | ||
start_from_left = not start_from_left | ||
return zigzag_traversed_values | ||
``` | ||
|
||
stackをつかったDFS | ||
```python | ||
from collections import deque | ||
|
||
|
||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
zigzag_traversed_values = [] | ||
nodes_and_levels = [(root, 0)] | ||
while nodes_and_levels: | ||
node, level = nodes_and_levels.pop() | ||
if node is None: | ||
continue | ||
while not len(zigzag_traversed_values) > level: | ||
zigzag_traversed_values.append(deque()) | ||
if level % 2: | ||
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. この書き方少し不安になります。 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. 確かにそうですね。ありがとうございます |
||
zigzag_traversed_values[level].append(node.val) | ||
else: | ||
zigzag_traversed_values[level].appendleft(node.val) | ||
Comment on lines
+153
to
+156
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. 深さごとの部屋 |
||
nodes_and_levels.append((node.left, level + 1)) | ||
nodes_and_levels.append((node.right, level + 1)) | ||
zigzag_traversed_values = [ | ||
list(values) for values in zigzag_traversed_values | ||
] | ||
return zigzag_traversed_values | ||
``` | ||
|
||
recursiveなDFS | ||
|
||
preorder traversalをしながら、けれど値はzigzagに集めていくというののいい関数名が思いつかなかった。 | ||
関数名に動作であるpreorderをいれて、中の変数にzigzagを入れた。 | ||
```python | ||
from collections import deque | ||
|
||
|
||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
def traverse_tree_in_preorder( | ||
node: Optional[TreeNode], | ||
level: int | ||
) -> None: | ||
if node is None: | ||
return | ||
nonlocal zigzag_traversed_values | ||
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. 確認ですが、これは、なくてもいい、であってますか。 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. When the definition of a function or class is nested (enclosed) within the definitions of other functions, its nonlocal scopes are the local scopes of the enclosing functions. 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. この問題で、
今回の ざっくりいうと、イミュータブルなものを更新したければ 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. ちらっと書かれていますが、mutable, immutableというより、より厳密にはinner functionのなかでbind(今回はassignment)があるかどうかな気がします(各種bind)。 関数block内で定義されたオブジェクトのスコープは、(inner functionの定義内を含め)その関数定義内全てとなる。ただ、(inner functionなど)内部に新たなblockができ、そこで同じ名前で新たなbindを導入すると、その名前に紐づいたobjectは内部のblock内に限られる。(原文, blockの定義) 従って整理すると以下のような感じかと思います
実際に言語化するといい生理になりますね。コメントありがとうございます 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. 補足ありがとうございます。bindとは何かがわかっていなかったです。読んでみます。 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. Arai 60のBacktrackingの問題を解いていたのですが、書いていただいたこの辺りの理解がようやく深まった気がします。ありがとうございます。
|
||
while not len(zigzag_traversed_values) > level: | ||
zigzag_traversed_values.append(deque()) | ||
if level % 2: | ||
zigzag_traversed_values[level].appendleft(node.val) | ||
else: | ||
zigzag_traversed_values[level].append(node.val) | ||
traverse_tree_in_preorder(node.left, level + 1) | ||
traverse_tree_in_preorder(node.right, level + 1) | ||
|
||
zigzag_traversed_values = [] | ||
traverse_tree_in_preorder(root, 0) | ||
zigzag_traversed_values = [ | ||
list(values) for values in zigzag_traversed_values | ||
] | ||
return zigzag_traversed_values | ||
``` | ||
|
||
# step 3 | ||
|
||
```python | ||
class Solution: | ||
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
level_ordered_values = [] | ||
nodes_in_level = [root] | ||
while True: | ||
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. trueだとループの終了条件を読み取る時にbreakのあるところまで読む必要があり、コードを読む量が増えて読みづらくなってしまいます。 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. nodes_in_level に None のみが含まれている場合にもループを抜けたいのだと思います。そのため、直後の行で None を取り除き、そのあとで空かどうか判定しているのだと思います。 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. 補足ありがとうございます。 ただ、ループ始まってすぐのL207実行後にnodes_in_levelにNoneが含まれなくなる(変数の意味が少し変わる)こと、L207自体が少し複雑な書き方をしていること、指摘されている通りbreakかreturnを探さないといけないことなど、そもそもこの構造自体がすこし複雑だったかもなと今は思っています。 (書いた時は、なかなかうまく書けたんじゃないかと思ったんですが見返すと厳しいですね。いつもコメントありがとうございます) 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.
コメントありがとうございます、ちゃんと全部読めていなかったです。 |
||
nodes_in_level = list(filter(None, nodes_in_level)) | ||
if not nodes_in_level: | ||
break | ||
nodes_in_next_level = [] | ||
values_in_level = [] | ||
for node in nodes_in_level: | ||
values_in_level.append(node.val) | ||
nodes_in_next_level.append(node.left) | ||
nodes_in_next_level.append(node.right) | ||
nodes_in_level = nodes_in_next_level | ||
level_ordered_values.append(values_in_level) | ||
|
||
zigzag_traversed_values = [] | ||
start_from_left = True | ||
for left_to_right_values in level_ordered_values: | ||
if start_from_left: | ||
zigzag_traversed_values.append(left_to_right_values) | ||
else: | ||
right_to_left_values = list(reversed(left_to_right_values)) | ||
zigzag_traversed_values.append(right_to_left_values) | ||
start_from_left = not start_from_left | ||
return zigzag_traversed_values | ||
``` |
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.
先頭からの pop() をしていないため、 deque である必要がないように感じました。より軽い list を使ったほうがよいと思います。