Skip to content
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

Add 103. Binary Tree Zigzag Level Order Traversal.md #27

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

t0hsumi
Copy link
Owner

@t0hsumi t0hsumi commented Mar 6, 2025

) -> None:
if node is None:
return
nonlocal zigzag_traversed_values
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確認ですが、これは、なくてもいい、であってますか。

Copy link
Owner Author

@t0hsumi t0hsumi Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@olsen-blue olsen-blue Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この問題で、nonlocalを書かないとエラーになる理由が、やっと何となくわかりました。
https://github.com/olsen-blue/Arai60/pull/29/files#:~:text=return%20None-,nonlocal%20preorder_index,-preorder_index%20%2B%3D%201

nonlocalなしでpreorder_index = preorder_index + 1と代入(bound)の形で書くと、preorder_indexがローカル変数扱いになるということだったんですね。そんなローカル変数はないぞ、というエラー(UnboundLocalError)が実際出ていました。
nonlocalを書くと解決できていました。
https://docs.python.org/3/reference/executionmodel.html#naming-and-binding:~:text=If%20a%20name%20is%20bound%20in%20a%20block%2C%20it%20is%20a%20local%20variable%20of%20that%20block%2C%20unless%20declared%20as%20nonlocal%20or%20global.

今回のzigzag_traversed_valuesは、変数自体を更新しているわけではなく、変数自体に要素追加の更新をしているだけなので、nonlocal不要ということですかね。

ざっくりいうと、イミュータブルなものを更新したければnonlocalが必要で、ミュータブルなものの更新(要素追加など)であれば、nonlocal不要ということでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

The 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の定義)

従って整理すると以下のような感じかと思います

  • inner functionの中では、読み込み専用で使う分には(bindがないので)外側の関数の値を見ることができる。
  • 書き込みをしたい場合、immutableだと代入以外に変数名に紐づいた値を更新できないが、代入をするとその変数のスコープはinner function内に限られる。
    • nonlocalをつけることで、外側のスコープのものを引っ張ってこれるので書き込みも可能になる
  • mutableだと代入以外の方法でも中身の書き換えができるので、結果としてnonlocalを使わずに済む場合がある。

実際に言語化するといい生理になりますね。コメントありがとうございます

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

補足ありがとうございます。bindとは何かがわかっていなかったです。読んでみます。

zigzag_traversed_values = []
nodes_in_level = deque([root])
start_from_left = True
while True:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的にループ条件を指定できるのであればしたいなという気持ちになりました。仮にもっと長いソースで何か変更して無限ループになった時、ソースを読む負担が減るんじゃないかなと思いました。

def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
level_ordered_values = []
nodes_in_level = [root]
while True:
Copy link

@colorbox colorbox Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trueだとループの終了条件を読み取る時にbreakのあるところまで読む必要があり、コードを読む量が増えて読みづらくなってしまいます。
nodes_in_levelがemptyではない、という条件で良さそうに思います

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodes_in_level に None のみが含まれている場合にもループを抜けたいのだと思います。そのため、直後の行で None を取り除き、そのあとで空かどうか判定しているのだと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

補足ありがとうございます。
おっしゃる通り、nodes_in_levelには、Noneも気にせず入れていいという感じでやっています。したがって、この書き方で、L206のTrueをnot nodes_in_levelとしてもその条件が成立することはないので、そこだけを変更してもかえって複雑かなと思ってます。

ただ、ループ始まってすぐのL207実行後にnodes_in_levelにNoneが含まれなくなる(変数の意味が少し変わる)こと、L207自体が少し複雑な書き方をしていること、指摘されている通りbreakかreturnを探さないといけないことなど、そもそもこの構造自体がすこし複雑だったかもなと今は思っています。

(書いた時は、なかなかうまく書けたんじゃないかと思ったんですが見返すと厳しいですね。いつもコメントありがとうございます)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodes_in_level に None のみが含まれている場合にもループを抜けたいのだと思います

おっしゃる通り、nodes_in_levelには、Noneも気にせず入れていいという感じでやっています。

コメントありがとうございます、ちゃんと全部読めていなかったです。

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここで if not start_from_left: values_in_level.reverse() とすると下がいらなくなりますね。

Comment on lines +153 to +156
if level % 2:
zigzag_traversed_values[level].append(node.val)
else:
zigzag_traversed_values[level].appendleft(node.val)
Copy link

@olsen-blue olsen-blue Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

深さごとの部屋 deque() に入れる時の方向をappend, appendleft で切り替えるんですね。なるほどです。

class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
zigzag_traversed_values = []
nodes_in_level = deque([root])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

先頭からの pop() をしていないため、 deque である必要がないように感じました。より軽い list を使ったほうがよいと思います。

nodes_in_level = deque([root])
start_from_left = True
while True:
nodes_in_level = deque(filter(None, nodes_in_level))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None を取り除いているということが分かりにくく感じました。
nodes_in_level に入れる前に None であるかをチェックしてから入れたほうが分かりやすいと思います。あるいは、 nodes_in_level に入れ終わった直後にこの行を入れ、かつコメントで処理内容を補足するとよいと思いました。

def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
level_ordered_values = []
nodes_in_level = [root]
while True:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodes_in_level に None のみが含まれている場合にもループを抜けたいのだと思います。そのため、直後の行で None を取り除き、そのあとで空かどうか判定しているのだと思います。

Comment on lines +36 to +39
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)
Copy link

@nittoco nittoco Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には、本当に逆順で入っているかの確認のためにL29から32とL36から39をじっくり見比べる必要があり、あまり好みではないです。(depthが奇数の時reverseする、などの処理だけの方が読む負担が少ない)

nodes_in_level = list(filter(None, nodes_in_level))
if not nodes_in_level:
break
nodes_in_level.reverse()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これも結構読む時の認知負荷がありますね。
ここで毎回reverse()した結果、nodes_in_levelの処理とnode_in_next_levelへのappend処理がどの順で行われているのか追うのが大変でした

continue
while not len(zigzag_traversed_values) > level:
zigzag_traversed_values.append(deque())
if level % 2:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この書き方少し不安になります。
言語による差もあるので比較演算子を使うほうが無難かなと思います。
https://zenn.dev/bugbearr/articles/69e1101ccc676a4b28b6

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにそうですね。ありがとうございます

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants