Skip to content

Create 111. Minimum Depth of Binary Tree.md#22

Open
tokuhirat wants to merge 1 commit intomainfrom
111.-Minimum-Depth-of-Binary-Tree
Open

Create 111. Minimum Depth of Binary Tree.md#22
tokuhirat wants to merge 1 commit intomainfrom
111.-Minimum-Depth-of-Binary-Tree

Conversation

@tokuhirat
Copy link
Copy Markdown
Owner

This Problem
111. Minimum Depth of Binary Tree
Next Ploblem
617. Merge Two Binary Trees
言語: Python

min_depth = sys.maxsize

def minDepthHelper(node: Optional[TreeNode], depth: int):
nonlocal min_depth
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私は素直に値を返す方法の方が、nonlocalを使用して関数外の値を書き換える方法よりも好きです。引数以外の値にアクセスしていると、予期せず他の関数がその値を書き換えていたりしそうで少し怖い感覚があります。また、副作用はできるだけ抑えたいような気持ちがあります。

参考: https://discord.com/channels/1084280443945353267/1201211204547383386/1247145320098566144

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

素直に値を返す方法とは、再帰・帰りがけのパターンでしょうか。nonlocal が怖いという気持ちはわかります。再帰・行きがけで nonlocal 使わずに書く方法が分からなかったためこの書き方になりました。

Copy link
Copy Markdown

@huyfififi huyfififi May 21, 2025

Choose a reason for hiding this comment

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

はい。再帰・post-order traversalです。部下にsubtreeの最小の深さを報告してもらえばいける気がします。すみません、今AFKなので確認はできないのですが、トライしていただけると助かります

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

L52-L62 の書き方と思っているのですが別でしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

失礼しました、見落としていました。行きがけだとnonlocal を使用しないと解けないということですね。ありがとうございます!

Copy link
Copy Markdown

@huyfififi huyfififi May 21, 2025

Choose a reason for hiding this comment

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

すみません、変なことを言っていたら申し訳ないのですが、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)

Copy link
Copy Markdown
Owner Author

@tokuhirat tokuhirat May 21, 2025

Choose a reason for hiding this comment

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

min_depth をleft, right から受け取っているため、行きがけじゃないと思います。

Copy link
Copy Markdown

@huyfififi huyfififi May 21, 2025

Choose a reason for hiding this comment

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

確かにそうですね、Nodeの処理順が同じであることと、pre/post orderかどうかを混同してしまっていました。ご指摘いただいてありがとうございます!


min_depth = sys.maxsize

def minDepthHelper(node: Optional[TreeNode], depth: int):
Copy link
Copy Markdown

@huyfififi huyfififi May 21, 2025

Choose a reason for hiding this comment

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

引数には既に型ヒントをつけていただいているので、この関数は副作用を持つのだとアピールする目的で -> Noneをつけていただいた方が親切かなと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

忘れていました。ご確認ありがとうございます。

if root is None:
return 0

min_depth = sys.maxsize
Copy link
Copy Markdown

@huyfififi huyfififi May 21, 2025

Choose a reason for hiding this comment

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

sys.maxsizeって確かlistdictなどのデータ型の最大サイズですよね。多くの場合問題ではないでしょうが、表うる最大の数として使用するのは抵抗感があります。sys.maxsizeを超えるint型は全然ありうるので、あらゆる数より大きい数を表すならmath.inffloat("inf")が無難なような気がします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ご指摘はその通りと思います。ただ math.inf や float("inf") は float なのでそれはそれでどうなんだろうとも思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

悩みどころですよね😓

min_depth = sys.maxsize

def minDepthHelper(node: Optional[TreeNode], depth: int):
nonlocal min_depth
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

referenceを参照していなかったな、と反省。
https://docs.python.org/3/reference/simple_stmts.html#nonlocal

stack.append(["up", node, parent, depth])
continue

stack.append([direction, node, parent, depth])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これやるならクラスを用意しても良いかなと思いました

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。想定されているクラスとは、StackEntry のようなものか StackManager みたいなものか、どちらのイメージでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。
当初 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
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

読みやすかったです。
次の探索対象たちをとりあえずNoneでも候補に入れて、更新時にfilterをかけるのもありですね。
nodes = list(filter(None, next_nodes))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

その書き方もできますね。filer の第一引数に None を入れた場合の挙動は知りませんでした。ありがとうございます。
https://docs.python.org/3.13/library/functions.html#filter

- float('inf') の人もいる。
https://github.com/Mike0121/LeetCode/pull/11/files

DFS (再帰): 行きがけ。行きがけだと枝刈りできる。わかりやすくはない。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

別のコメントでも議論されておりますが、行きがけ再帰について、自分は以下のものを考えました。
帰るときに場合分けを詰め込みました。

関数にするかどうか、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)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。
ご提示いただいたコードも root の子を処理した結果を元に root の処理をしているので行きがけではない気がします。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ありがとうございます。自分が意図をくみ取れていないようでした。自分の意図としては、depth に注目したとき、それを行きがけに更新しているというもので、帰りがけの処理がないというわけではありませんでした。

帰りがけに処理をしないとなると難しいですね。


nodes = [root]
depth = 0
while nodes:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

個人的にですが、while True にするのもありかなと感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

以下のコメントをもらったこともあり、nodes にしております。
意味の面(BFSの処理)からもnodesがわかりやすいかと思いました。

while Trueを使うときは「内部で確実にreturnかbreakが起こり無限ループが発生しないか」というようにかなり身構えますね。
#11 (comment)



コードを書きながら left と right をタプルにして stack に積もうとしたりしてだいぶ迷走した。
手で作業して言語化したら書けた。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

参考になります。余裕があるときに自分もこの形を負担なく書けるように練習しようと思います。

next_nodes.append(node.left)
if node.right is not None:
next_nodes.append(node.right)
nodes = next_nodes
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私は depth = 1 ではじめて、depth += 1 を nodes = next_nodes の後ろに移動させると、nodes の中身と depth が常に対応して少し気分がいいかなと思いますが、しかし趣味の範囲でしょう。

よく調べて考えていると思います。

Copy link
Copy Markdown
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.

6 participants