-
Notifications
You must be signed in to change notification settings - Fork 0
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
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,182 @@ | ||
## 何も見ずに解いてみる | ||
|
||
```python | ||
class Solution: | ||
def minDepth(self, root: Optional[TreeNode]) -> int: | ||
if not root: | ||
return 0 | ||
queue = deque([(root, 1)]) | ||
while queue: | ||
node, depth = queue.popleft() | ||
if not node.left and not node.right: | ||
return depth | ||
if node.left: | ||
queue.append((node.left, depth + 1)) | ||
if node.right: | ||
queue.append((node.right, depth + 1)) | ||
``` | ||
|
||
## 色々調べてみる | ||
|
||
https://github.com/SuperHotDogCat/coding-interview/pull/36/files | ||
https://github.com/hayashi-ay/leetcode/pull/26/files | ||
https://github.com/shintaro1993/arai60/pull/26/files | ||
https://github.com/tokuhirat/LeetCode/pull/22/files | ||
|
||
tokuhiratさんが書いていた帰りがけにdepthを伝えるstackのDFSみたいな書き方を試してみた。 | ||
|
||
```python | ||
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 | ||
explored: bool = False | ||
|
||
stack = [StackEntry(root, None, 1)] | ||
while stack: | ||
entry = stack[-1] | ||
if not entry.explored: | ||
if not entry.node.left and not entry.node.right: | ||
entry.explored = True | ||
continue | ||
if entry.node.left: | ||
stack.append(StackEntry(entry.node.left, entry.node, entry.depth + 1)) | ||
if entry.node.right: | ||
stack.append(StackEntry(entry.node.right, entry.node, entry.depth + 1)) | ||
else: # If stack[-1] is explored | ||
stack.pop() | ||
if not entry.parent: | ||
return entry.depth | ||
if stack[-1].parent == entry.parent: # If stack[-1] is sibling | ||
sibling_entry = stack.pop() | ||
if not sibling_entry.explored: | ||
stack.append(entry) | ||
stack.append(sibling_entry) | ||
continue | ||
parent_entry = stack[-1] | ||
parent_entry.depth = min(entry.depth, sibling_entry.depth) | ||
parent_entry.explored = True | ||
else: # If stack[-1] is parent | ||
parent_entry = stack[-1] | ||
parent_entry.depth = entry.depth | ||
parent_entry.explored = True | ||
``` | ||
|
||
こんな感じで常に直前のstackが親になるようにすることもできそう | ||
is_leftはLiteral["left", "right"]とかにした方がわかりやすいかな? | ||
math.infを使うのは分かりづらそう、Noneで初期化・分岐した方が良いか | ||
|
||
```python | ||
from dataclasses import dataclass | ||
from math import inf, isinf | ||
|
||
class Solution: | ||
def minDepth(self, root: Optional[TreeNode]) -> int: | ||
if not root: | ||
return 0 | ||
|
||
@dataclass | ||
class StackEntry: | ||
node: TreeNode | ||
parent: Optional[TreeNode] | ||
is_left: bool | ||
depth: int | ||
min_depth: int = math.inf | ||
|
||
stack = [StackEntry(root, None, 1, False)] | ||
while True: | ||
entry = stack[-1] | ||
if isinf(entry.min_depth): | ||
if not entry.node.left and not entry.node.right: | ||
entry.min_depth = entry.depth | ||
continue | ||
if entry.node.left: | ||
stack.append(StackEntry(entry.node.left, entry.node, True, entry.depth + 1)) | ||
elif entry.node.right: | ||
stack.append(StackEntry(entry.node.right, entry.node, False, entry.depth + 1)) | ||
else: | ||
stack.pop() | ||
if not stack: # If the entry is the root | ||
return entry.min_depth | ||
parent_entry = stack[-1] | ||
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. 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, [], [])] などとすればいいのではないでしょうか。 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. あ、なるほど!こういう感じですかね。 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] 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. あ、はい。そうですね。 entry がうるさい感じもするので、astuple で unpack して そうするならば、StackEntry はあまり読解の役に立っていないように感じるので、ただの tuple でもいいのではないかと迷いますね。 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. 参考までに C++ だとキーワード引数はありませんが、引数の意味にコメントをつけることもあります。 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. minにもdefaultがあったんですね。キーワード引数の話も参考になります。 |
||
parent_entry.min_depth = min(entry.min_depth, parent_entry.min_depth) | ||
if entry.is_left and parent_entry.node.right: | ||
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. L103 が elif なのと is_left を処理した後に right がいるかを確認していることで常に直前の stack の要素が親になり swap がいらなくなっているんですね。なるほどと思いました。 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. たぶん自分の通りがけ(=行きがけ ですかね?)と帰りがけの認識が結構怪しいんですが、is_leftという情報を通るときに持たせているから通りがけの要素も持っているということでしょうか。 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. 最初にノードに訪れた時に(子ノードを見る前に)処理をする場合が行きがけ、ノードを左から右に通る時(左の子を処理した後)に処理をする場合が通りがけ、左右の子を処理した後にノードの処理をする場合が帰りがけ、だと思います。まず left の子を処理した後に L110 でその情報を親に反映させているため、通りがけの要素もあるかなということです。 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. なるほどです! |
||
stack.append(StackEntry(parent_entry.node.right, parent_entry.node, False, parent_entry.depth + 1)) | ||
``` | ||
|
||
もっとわかりやすく書くならこんな感じ?逆にわかりにくいかも | ||
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. 結構難しかったです。読み間違えてたら教えてください。children_min_depths が子ノードが探索済みかと深さを表していると思っています。その情報を node の entry が持っているのは意外なような気がします。 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.
その認識であっております。
直前の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 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. 前提として、__post_init__ を使っていない一つ上のコード (A) の方が、__post_init__ を使ったコード (B) と比較して自然に思いましたというコメントです。必要な情報や手続きはお書きいただいたとおりだと思います。 コードAでは子がいない時にノードの min_depth を確定させて探索済みとしています。これを親の min_depth に反映させています。同じ階層の探索を切り上げて良いかどうかは、entry に左の子かを表す情報を入れるか、兄弟がいるかを stack を遡ってみる必要があります。 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. ありがとうございます。 迷うところですが、個人的にはもともとのグラフで兄弟ノード同士は繋がっていないので(TreeNodeが持っているのは子の情報のみ)、あるノードのentryに関するループに、そのノードの兄弟が探索済みかどうかを判断する責任を帰着させるのはちょっと分かりにくいかなと感じるところがありました。 ちょっと適切な例えかどうかはわかりませんが、それぞれの人に上司が一人、部下が二人いるとすれば、最終的な目的は最も優秀な成果を持ってきた末端の成果を一番上に伝えることだと思います。 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. 上司部下の例と、odaさんが提案された書き方を見ると、node の entry が子供の深さを持っているのも自然だなと思えるようになりました。 こちらこそありがとうございます! |
||
|
||
```python | ||
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] | ||
pos_to_parent: Optional[Literal["left", "right"]] | ||
depth: int | ||
def __post_init__(self): | ||
self.children_min_depths = {} | ||
if self.node.left: | ||
self.children_min_depths["left"] = None | ||
if self.node.right: | ||
self.children_min_depths["right"] = None | ||
|
||
stack = [StackEntry(root, None, None, 1)] | ||
while True: | ||
entry = stack[-1] | ||
if "left" in entry.children_min_depths and entry.children_min_depths["left"] is None: | ||
stack.append(StackEntry( | ||
node=entry.node.left, | ||
parent=entry.node, | ||
pos_to_parent="left", | ||
depth=entry.depth + 1, | ||
)) | ||
continue | ||
if "right" in entry.children_min_depths and entry.children_min_depths["right"] is None: | ||
stack.append(StackEntry( | ||
node=entry.node.right, | ||
parent=entry.node, | ||
pos_to_parent="right", | ||
depth=entry.depth + 1, | ||
)) | ||
continue | ||
min_depth = min(entry.children_min_depths.values()) if entry.node.left or entry.node.right else entry.depth | ||
stack.pop() | ||
if not stack: | ||
return min_depth | ||
stack[-1].children_min_depths[entry.pos_to_parent] = min_depth | ||
``` | ||
|
||
## 最終コード | ||
|
||
```python | ||
from collections import deque | ||
|
||
class Solution: | ||
def minDepth(self, root: Optional[TreeNode]) -> int: | ||
if not root: | ||
return 0 | ||
queue = deque([(root, 1)]) | ||
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_depths などのような名前をつけるかなと思いました。 |
||
while queue: | ||
node, depth = queue.popleft() | ||
if not node.left and not node.right: | ||
return depth | ||
if node.left: | ||
queue.append((node.left, depth + 1)) | ||
if node.right: | ||
queue.append((node.right, depth + 1)) | ||
``` |
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.
引数の順番が違うようですね。
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.
あ、ご指摘の通りです。失礼しました。