Skip to content

111. Minimum Depth of Binary Tree #22

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

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

111. Minimum Depth of Binary Tree #22

wants to merge 1 commit into from

Conversation

potrue
Copy link
Owner

@potrue potrue commented May 28, 2025

return entry.min_depth
parent_entry = stack[-1]
parent_entry.min_depth = min(entry.min_depth, parent_entry.min_depth)
if entry.is_left and parent_entry.node.right:

Choose a reason for hiding this comment

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

L103 が elif なのと is_left を処理した後に right がいるかを確認していることで常に直前の stack の要素が親になり swap がいらなくなっているんですね。なるほどと思いました。
ちなみに、この場合は、通りがけ+帰りがけになっていそうですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

たぶん自分の通りがけ(=行きがけ ですかね?)と帰りがけの認識が結構怪しいんですが、is_leftという情報を通るときに持たせているから通りがけの要素も持っているということでしょうか。

Choose a reason for hiding this comment

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

最初にノードに訪れた時に(子ノードを見る前に)処理をする場合が行きがけ、ノードを左から右に通る時(左の子を処理した後)に処理をする場合が通りがけ、左右の子を処理した後にノードの処理をする場合が帰りがけ、だと思います。まず left の子を処理した後に L110 でその情報を親に反映させているため、通りがけの要素もあるかなということです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほどです!
理解できました。ありがとうございます。

depth: int
min_depth: int = math.inf

stack = [StackEntry(root, None, 1, False)]

Choose a reason for hiding this comment

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

引数の順番が違うようですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あ、ご指摘の通りです。失礼しました。

stack.append(StackEntry(parent_entry.node.right, parent_entry.node, False, parent_entry.depth + 1))
```

もっとわかりやすく書くならこんな感じ?逆にわかりにくいかも

Choose a reason for hiding this comment

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

結構難しかったです。読み間違えてたら教えてください。children_min_depths が子ノードが探索済みかと深さを表していると思っています。その情報を node の entry が持っているのは意外なような気がします。

Copy link
Owner Author

@potrue potrue May 28, 2025

Choose a reason for hiding this comment

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

children_min_depths が子ノードが探索済みかと深さを表していると思っています。

その認識であっております。

その情報を node の entry が持っているのは意外なような気がします。

直前のstackの要素が常に親になるようになっている場合、子ノードが探索済みかどうかと深さを表す変数は何らかの形でentryに持たせるか、あるいは現在チェックしているentryの側から親のentryとは別にもう一個スタックを遡るなどして兄弟ノードの状態をチェックする要がある気がするのですが、どうでしょうか?

今書いてみたこちらのほうがわかりやすいかもしれません。

from dataclasses import dataclass

class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0

        @dataclass
        class StackEntry:
            node: TreeNode
            parent: Optional[TreeNode]
            depth: int
            def __post_init__(self):
                self.child_to_min_depth = {}
                if self.node.left:
                    self.child_to_min_depth[self.node.left] = None
                if self.node.right:
                    self.child_to_min_depth[self.node.right] = None

        stack = [StackEntry(root, None, 1)]
        while True:
            entry = stack[-1]
            next_child_to_explore = None
            for child, min_depth in entry.child_to_min_depth.items():
                if not min_depth:
                    next_child_to_explore = child
                    break
            if next_child_to_explore:
                stack.append(StackEntry(next_child_to_explore, entry.node, entry.depth + 1))
                continue
            min_depth = min(entry.child_to_min_depth.values()) if entry.child_to_min_depth else entry.depth
            stack.pop()
            if not stack:
                return min_depth
            stack[-1].child_to_min_depth[entry.node] = min_depth

Choose a reason for hiding this comment

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

前提として、__post_init__ を使っていない一つ上のコード (A) の方が、__post_init__ を使ったコード (B) と比較して自然に思いましたというコメントです。必要な情報や手続きはお書きいただいたとおりだと思います。
長くなってしまいましたが処理を言語化してしまいました。Aの方が自然に思いましたがいかがでしょうか。

コードAでは子がいない時にノードの min_depth を確定させて探索済みとしています。これを親の min_depth に反映させています。同じ階層の探索を切り上げて良いかどうかは、entry に左の子かを表す情報を入れるか、兄弟がいるかを stack を遡ってみる必要があります。
コードBでは子がいない時にノードの深さを取得し、その親 (stack[-1]) の entry の子ノードの深さ (child_to_min_depth) に反映させ探索済みとしています。あるノードの深さは、子ノードがいれば小さい方の深さとなります。コードAと比較して回りくどく感じてしまいました。代わりにあるノードが探索済みかは child_to_min_depth.values に 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.

ありがとうございます。
手続きについては、言語化していただいた通りだと思います!

迷うところですが、個人的にはもともとのグラフで兄弟ノード同士は繋がっていないので(TreeNodeが持っているのは子の情報のみ)、あるノードのentryに関するループに、そのノードの兄弟が探索済みかどうかを判断する責任を帰着させるのはちょっと分かりにくいかなと感じるところがありました。

ちょっと適切な例えかどうかはわかりませんが、それぞれの人に上司が一人、部下が二人いるとすれば、最終的な目的は最も優秀な成果を持ってきた末端の成果を一番上に伝えることだと思います。
上司に自分のチームの成果を伝えるとき、その伝え方が
「ボスの他の部下にも確認を取っておいたんですが、ボスの下のチームの中でもっとも優秀な成果はこれだったみたいですよ」であるよりは、
「うちのチームの中でもっとも優秀な成果はこれでした。他のチームとはよしなに比較しておいてください」の方が自然な気がする、というような感じです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

話は変わりますが、いつも詳細なレビューありがとうございます。コードの方も毎回参考にさせていただいております 🙏

Choose a reason for hiding this comment

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

上司部下の例と、odaさんが提案された書き方を見ると、node の entry が子供の深さを持っているのも自然だなと思えるようになりました。

こちらこそありがとうございます!

def minDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
queue = deque([(root, 1)])

Choose a reason for hiding this comment

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

読みやすかったです。個人的には nodes_and_depths などのような名前をつけるかなと思いました。

stack.pop()
if not stack: # If the entry is the root
return entry.min_depth
parent_entry = stack[-1]
Copy link

Choose a reason for hiding this comment

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

stack[-1] で parent を探し出して、そこに対して書き込みをしようとしているのが、比較的パズル度合いを上げているように思います。

やりたいことは再帰的に root.left と root.right を呼び出して、その結果を書き込む場所があればいいので、StackEntry に空のリストへの参照を置けばよいのではないですか。たとえば、

class StackEntry:
    node: TreeNode
    depth: int
    return_value: List[]  # 返り値
    left_value: List[]  # 呼び出し結果を受け取る
    right_value: List[]  # 呼び出し結果を受け取る

として、

result = []
stack = [StackEntry(root, 1, result, [], [])] 

などとすればいいのではないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あ、なるほど!こういう感じですかね。
見つけてもらう代わりに親からも子からも使えるポインタみたいなものを伝えていくということですか。

from dataclasses import dataclass

class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
            
        @dataclass
        class StackEntry:
            node: TreeNode
            depth: int
            return_value: List[int]
            left_value: List[int]
            right_value: List[int]

        result = []
        stack = [StackEntry(root, 1, result, [], [])]
        while stack:
            entry = stack[-1]
            if entry.node.left and not entry.left_value:
                stack.append(StackEntry(entry.node.left, entry.depth + 1, entry.left_value, [], []))
                continue
            if entry.node.right and not entry.right_value:
                stack.append(StackEntry(entry.node.right, entry.depth + 1, entry.right_value, [], []))
                continue
            if not entry.left_value and not entry.right_value:
                min_depth = entry.depth
            else:
                min_depth = min(value[0] for value in (entry.left_value, entry.right_value) if value)
            entry.return_value.append(min_depth)
            stack.pop()
        return result[0]

Copy link

Choose a reason for hiding this comment

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

あ、はい。そうですね。
あとは、min((...), default=entry.depth) 使うと min_depth の計算一行になりますかね。

entry がうるさい感じもするので、astuple で unpack して node, depth, return_value, left_value, right_value = astuple(stack[-1]) (かな?) してもいいように思う一方で、
https://docs.python.org/3/library/dataclasses.html#dataclasses.astuple

そうするならば、StackEntry はあまり読解の役に立っていないように感じるので、ただの tuple でもいいのではないかと迷いますね。
namedtuple にしたらコメントくらいになりますか。

Copy link

Choose a reason for hiding this comment

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

参考までに C++ だとキーワード引数はありませんが、引数の意味にコメントをつけることもあります。
https://discord.com/channels/1084280443945353267/1348344197647695873/1376980153204473869

Copy link
Owner Author

Choose a reason for hiding this comment

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

minにもdefaultがあったんですね。キーワード引数の話も参考になります。

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.

4 participants