Conversation
| min_depth = sys.maxsize | ||
|
|
||
| def minDepthHelper(node: Optional[TreeNode], depth: int): | ||
| nonlocal min_depth |
There was a problem hiding this comment.
私は素直に値を返す方法の方が、nonlocalを使用して関数外の値を書き換える方法よりも好きです。引数以外の値にアクセスしていると、予期せず他の関数がその値を書き換えていたりしそうで少し怖い感覚があります。また、副作用はできるだけ抑えたいような気持ちがあります。
参考: https://discord.com/channels/1084280443945353267/1201211204547383386/1247145320098566144
There was a problem hiding this comment.
素直に値を返す方法とは、再帰・帰りがけのパターンでしょうか。nonlocal が怖いという気持ちはわかります。再帰・行きがけで nonlocal 使わずに書く方法が分からなかったためこの書き方になりました。
There was a problem hiding this comment.
はい。再帰・post-order traversalです。部下にsubtreeの最小の深さを報告してもらえばいける気がします。すみません、今AFKなので確認はできないのですが、トライしていただけると助かります
There was a problem hiding this comment.
L52-L62 の書き方と思っているのですが別でしょうか?
There was a problem hiding this comment.
失礼しました、見落としていました。行きがけだとnonlocal を使用しないと解けないということですね。ありがとうございます!
There was a problem hiding this comment.
すみません、変なことを言っていたら申し訳ないのですが、min_depthを明示的に引数に渡すことで、関数外の状態によらない解法になりませんか?
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
def minDepthHelper(node: Optional[TreeNode], depth: int, min_depth: int = float("inf")) -> int:
if node is None:
return min_depth
if node.left is None and node.right is None:
min_depth = depth
if depth >= min_depth:
return min_depth
min_depth = minDepthHelper(node.left, depth + 1, min_depth)
min_depth = minDepthHelper(node.right, depth + 1, min_depth)
return min_depth
return minDepthHelper(root, 1)There was a problem hiding this comment.
min_depth をleft, right から受け取っているため、行きがけじゃないと思います。
There was a problem hiding this comment.
確かにそうですね、Nodeの処理順が同じであることと、pre/post orderかどうかを混同してしまっていました。ご指摘いただいてありがとうございます!
|
|
||
| min_depth = sys.maxsize | ||
|
|
||
| def minDepthHelper(node: Optional[TreeNode], depth: int): |
There was a problem hiding this comment.
引数には既に型ヒントをつけていただいているので、この関数は副作用を持つのだとアピールする目的で -> Noneをつけていただいた方が親切かなと思います。
| if root is None: | ||
| return 0 | ||
|
|
||
| min_depth = sys.maxsize |
There was a problem hiding this comment.
sys.maxsizeって確かlistやdictなどのデータ型の最大サイズですよね。多くの場合問題ではないでしょうが、表うる最大の数として使用するのは抵抗感があります。sys.maxsizeを超えるint型は全然ありうるので、あらゆる数より大きい数を表すならmath.infかfloat("inf")が無難なような気がします。
There was a problem hiding this comment.
ご指摘はその通りと思います。ただ math.inf や float("inf") は float なのでそれはそれでどうなんだろうとも思います。
| min_depth = sys.maxsize | ||
|
|
||
| def minDepthHelper(node: Optional[TreeNode], depth: int): | ||
| nonlocal min_depth |
There was a problem hiding this comment.
referenceを参照していなかったな、と反省。
https://docs.python.org/3/reference/simple_stmts.html#nonlocal
| stack.append(["up", node, parent, depth]) | ||
| continue | ||
|
|
||
| stack.append([direction, node, parent, depth]) |
There was a problem hiding this comment.
コメントありがとうございます。想定されているクラスとは、StackEntry のようなものか StackManager みたいなものか、どちらのイメージでしょうか?
There was a problem hiding this comment.
StackEntry のほうだと思いました。
また、 namedtuple・typing.TypedDict・dataclasses あたりを使うと良さそうだと思いました。
https://docs.python.org/ja/3.13/library/collections.html#collections.namedtuple
https://docs.python.org/ja/3.13/library/typing.html#typing.TypedDict
https://docs.python.org/ja/3.13/library/dataclasses.html
StackEntry = namedtuple('StackEntry', ['direction', 'node', 'parent', 'depth'])
entry = StackEntry("down", root, None, 1)
class StackEntry(TypedDict):
direction: str
node: TreeNode
parent: TreeNode
depth: int
entry = StackEntry("down", root, None, 1)
@dataclass
class StackEntry:
direction: str
node: TreeNode
parent: TreeNode
depth: int
entry = StackEntry("down", root, None, 1)There was a problem hiding this comment.
コメントありがとうございます。
当初 namedtuple でStackEntry を書いていましたが、値の入れ替えに _replace を使い出したらわかりにくくなったので、各種操作を持つ StackManager を書いたら良いのか、それはそれで複雑になる、と考えていました。素直に entry を作り直すか、dataclass など使えばわかりやすいと、コメントいただき考え直しました。
stack[-1][2] でparentなど参照していた部分についてはわかりやすくなった気がします。
あとはIDEで書いていたらメンバー変数見えると思うので格段に書きやすくなりそうです。
from dataclasses import dataclass
@dataclass
class StackEntry:
direction: str
node: TreeNode
parent: TreeNode | None
depth: int
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
def update_depth_post_order(depth):
entry = stack.pop()
entry.direction = "up"
entry.depth = depth
stack.append(entry)
stack = [StackEntry("down", root, None, 1)]
while stack:
entry = stack.pop()
if entry.direction == "down":
if entry.node.left is None and entry.node.right is None:
entry.direction = "up"
stack.append(entry)
continue
stack.append(entry)
if entry.node.left is not None:
stack.append(StackEntry("down", entry.node.left, entry.node, entry.depth + 1))
if entry.node.right is not None:
stack.append(StackEntry("down", entry.node.right, entry.node, entry.depth + 1))
else:
if entry.parent is None:
return entry.depth
if stack[-1].parent is entry.parent and stack[-1].direction == "down":
# another children is not traversed
# swap to traverse another children
another = stack.pop()
stack.append(entry)
stack.append(another)
continue
if stack[-1].parent is entry.parent and stack[-1].direction == "up":
# two children traversed
another = stack.pop()
min_child_depth = min(entry.depth, another.depth)
update_depth_post_order(min_child_depth)
continue
# one child exists
update_depth_post_order(entry.depth)| if node.right is not None: | ||
| next_nodes.append(node.right) | ||
| nodes = next_nodes | ||
| ``` |
There was a problem hiding this comment.
読みやすかったです。
次の探索対象たちをとりあえずNoneでも候補に入れて、更新時にfilterをかけるのもありですね。
nodes = list(filter(None, next_nodes))
There was a problem hiding this comment.
その書き方もできますね。filer の第一引数に None を入れた場合の挙動は知りませんでした。ありがとうございます。
https://docs.python.org/3.13/library/functions.html#filter
| - float('inf') の人もいる。 | ||
| https://github.com/Mike0121/LeetCode/pull/11/files | ||
|
|
||
| DFS (再帰): 行きがけ。行きがけだと枝刈りできる。わかりやすくはない。 |
There was a problem hiding this comment.
別のコメントでも議論されておりますが、行きがけ再帰について、自分は以下のものを考えました。
帰るときに場合分けを詰め込みました。
関数にするかどうか、if 文の順番、(sys.maxsize を使うと場合分けシンプルにできるかもしれません?)など、他の形にも変形できると思いますが、何かの参考になりますと幸いです。
class Solution:
def has_no_child(self, node: Optional[TreeNode]) -> bool:
return node.left is None and node.right is None
def has_only_left_child(self, node: Optional[TreeNode]) -> bool:
return node.left is not None and node.right is None
def has_only_right_child(self, node: Optional[TreeNode]) -> bool:
return node.left is None and node.right is not None
def get_min_depth(self, root: Optional[TreeNode], depth: int) -> int:
if root is None:
return depth
depth += 1
left_depth = self.get_min_depth(root.left, depth)
right_depth = self.get_min_depth(root.right, depth)
if self.has_no_child(root):
return depth
if self.has_only_left_child(root):
return left_depth
if self.has_only_right_child(root):
return right_depth
return min(left_depth, right_depth)
def minDepth(self, root: Optional[TreeNode]) -> int:
return self.get_min_depth(root, 0)
There was a problem hiding this comment.
コメントありがとうございます。
ご提示いただいたコードも root の子を処理した結果を元に root の処理をしているので行きがけではない気がします。
There was a problem hiding this comment.
ありがとうございます。自分が意図をくみ取れていないようでした。自分の意図としては、depth に注目したとき、それを行きがけに更新しているというもので、帰りがけの処理がないというわけではありませんでした。
帰りがけに処理をしないとなると難しいですね。
|
|
||
| nodes = [root] | ||
| depth = 0 | ||
| while nodes: |
There was a problem hiding this comment.
個人的にですが、while True にするのもありかなと感じました。
There was a problem hiding this comment.
以下のコメントをもらったこともあり、nodes にしております。
意味の面(BFSの処理)からもnodesがわかりやすいかと思いました。
while Trueを使うときは「内部で確実にreturnかbreakが起こり無限ループが発生しないか」というようにかなり身構えますね。
#11 (comment)
|
|
||
|
|
||
| コードを書きながら left と right をタプルにして stack に積もうとしたりしてだいぶ迷走した。 | ||
| 手で作業して言語化したら書けた。 |
There was a problem hiding this comment.
参考になります。余裕があるときに自分もこの形を負担なく書けるように練習しようと思います。
| next_nodes.append(node.left) | ||
| if node.right is not None: | ||
| next_nodes.append(node.right) | ||
| nodes = next_nodes |
There was a problem hiding this comment.
私は depth = 1 ではじめて、depth += 1 を nodes = next_nodes の後ろに移動させると、nodes の中身と depth が常に対応して少し気分がいいかなと思いますが、しかし趣味の範囲でしょう。
よく調べて考えていると思います。
There was a problem hiding this comment.
改めて考えてみると、ご提案の書き方の方が自然に思えてきました。コメントありがとうございます。
This Problem
111. Minimum Depth of Binary Tree
Next Ploblem
617. Merge Two Binary Trees
言語: Python