-
Notifications
You must be signed in to change notification settings - Fork 0
105. Construct Binary Tree from Preorder and Inorder Traversal #42
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,165 @@ | ||
| # 105. Construct Binary Tree from Preorder and Inorder Traversal | ||
| - 問題: https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ | ||
| - 言語: Python | ||
|
|
||
| ## Step1 | ||
| ### 考えていた方針 | ||
| - 「二分木が与えられた時に、DFSで左からpre-order, in-orderで節の値を出力する」の逆の操作を行う | ||
| - pre-orderのある値Aの同じインデックスのin-orderの値Bとした場合、Aの次の節の値はBである | ||
| - 例: `preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]` なら、 `3` の次の節の値は `9` | ||
| - in-orderのある値Cと一つ前のインデックスのpre-orderの値が同じ場合、その節は葉である | ||
| - 例: `preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]` なら、 `inorder[1]` = `preorder[0]` | ||
| - 最後の葉のみ、値は同じになる | ||
| - 二分木の条件があるため、ある節に「左節、右節が存在」もしくは「存在しない」のどちらかである | ||
| - 入力値の最大長 $3000$ , 計算量 $O(n)$ で、Pythonの場合: $10^{7}$ ステップ/秒 のため、$3000 / 10^{7} ≒ 0.3ms$ | ||
| - ここまで考えていたら15分経過したので一旦正答を見る | ||
|
|
||
| ## Step2 | ||
| ### 正しい方針 | ||
| ``` | ||
| preorder = [root, left subtree..., right subtree...] | ||
| inorder = [left subtree..., root, right subtree...] | ||
| ``` | ||
| - preorder は `root < left < right` の順序 | ||
| - inorder は `left < root < right` の順序 | ||
|
|
||
| 1. preorder の先頭が現在の部分木の root | ||
| 2. その root を inorder の中で探す | ||
| 3. root より左側が左部分木 | ||
| 4. root より右側が右部分木 | ||
| 5. 左部分木の大きさが分かるので、preorder も左右に分割できる | ||
|
|
||
| #### 感想 | ||
| - 部分木 subtree の意識が自分の中になかった | ||
| - DFS traversal の性質の理解が甘かった | ||
|
|
||
| ### 正答 | ||
| #### 再帰DFS版 | ||
| ```py | ||
| class Solution: | ||
| def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: | ||
| in_pos = {val: i for i, val in enumerate(inorder)} | ||
|
|
||
| def build_nodes(pre_l, pre_r, in_l, in_r): | ||
| if pre_l > pre_r: | ||
| return None | ||
|
|
||
| root_val = preorder[pre_l] | ||
| root = TreeNode(root_val) | ||
|
|
||
| mid = in_pos[root_val] | ||
| left_size = mid - in_l | ||
|
|
||
| root.left = build_nodes( | ||
| pre_l + 1, | ||
| pre_l + left_size, | ||
| in_l, | ||
| mid - 1 | ||
| ) | ||
|
|
||
| root.right = build_nodes( | ||
| pre_l + left_size + 1, | ||
| pre_r, | ||
| mid + 1, | ||
| in_r | ||
| ) | ||
|
|
||
| return root | ||
|
|
||
| return build_nodes(0, len(preorder) - 1, 0, len(inorder) - 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. 変数名は略さずに書いたほうが良いかなと思いました。また、 class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
value_to_inoder_index = {val: i for i, val in enumerate(inorder)}
def build_nodes(preorder_left, preorder_right, inorder_left, inorder_right):
if preorder_left > preorder_right:
return None
root_value = preorder[preorder_left]
root = TreeNode(root_value)
root_inorder_index = value_to_inoder_index[root_value]
left_subtree_size = root_inorder_index - inorder_left
root.left = build_nodes(preorder_left + 1, preorder_left + left_subtree_size, inorder_left, root_inorder_index - 1)
root.right = build_nodes(preorder_left + left_subtree_size + 1, preorder_right, root_inorder_index + 1, inorder_right)
return root
return build_nodes(0, len(preorder) - 1, 0, len(inorder) - 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. また、処理は若干複雑だと感じたので、適宜コメントを添えたりしてもいいのかなと思いました。試しに書いてみましたが、全体的に冗長かもしれません。 class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
value_to_inorder_index = {value: index for index, value in enumerate(inorder)}
def build_subtree(preorder_start, preorder_end, inorder_start, inorder_end):
if preorder_start > preorder_end:
return None
# preorder
# [root][left subtree][right subtree]
root_value = preorder[preorder_start]
root = TreeNode(root_value)
# inorder
# [left subtree][root][right subtree]
root_inorder_index = value_to_inorder_index[root_value]
left_subtree_size = root_inorder_index - inorder_start
# Update subtree ranges.
left_subtree_preorder_start = preorder_start + 1
left_subtree_preorder_end = preorder_start + left_subtree_size
right_subtree_preorder_start = left_subtree_preorder_end + 1
right_subtree_preorder_end = preorder_end
left_subtree_inorder_start = inorder_start
left_subtree_inorder_end = root_inorder_index - 1
right_subtree_inorder_start = root_inorder_index + 1
right_subtree_inorder_end = inorder_end
root.left = build_subtree(
left_subtree_preorder_start,
left_subtree_preorder_end,
left_subtree_inorder_start,
left_subtree_inorder_end,
)
root.right = build_subtree(
right_subtree_preorder_start,
right_subtree_preorder_end,
right_subtree_inorder_start,
right_subtree_inorder_end,
)
return root
return build_subtree(0, len(preorder) - 1, 0, len(inorder) - 1) |
||
| ``` | ||
|
|
||
| #### iterative DFS版 | ||
|
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. 再帰版とアプローチが異なるようですので、簡単に説明があると読みやすいかと思います。 |
||
| ```py | ||
| class Solution: | ||
| def buildTree(self, preorder, inorder): | ||
| if not preorder: | ||
| return None | ||
|
|
||
| root = TreeNode(preorder[0]) | ||
| stack = [root] | ||
| in_i = 0 # 左方向にたどって必ず最初に訪問される節 | ||
|
|
||
| for val in preorder[1:]: | ||
| node = stack[-1] | ||
|
|
||
| # 全体の左部分木を作っている途中 | ||
| if node.val != inorder[in_i]: | ||
| node.left = TreeNode(val) | ||
| stack.append(node.left) | ||
| continue | ||
|
|
||
| # 一致した場合は、そのノードの左部分木とノード自身の処理が完了 | ||
| # 連続して一致する限りstackからpop | ||
| while stack and stack[-1].val == inorder[in_i]: | ||
| node = stack.pop() | ||
| in_i += 1 | ||
| # 右部分木を作る | ||
| node.right = TreeNode(val) | ||
| stack.append(node.right) | ||
|
|
||
| return root | ||
| ``` | ||
|
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. 関数化したほうが分かりやすいかもしれません。 class Solution:
def buildTree(self, preorder, inorder):
if not preorder:
return None
root = TreeNode(preorder[0])
right_subtree_parent_candidates = [root]
inorder_index = 0
def find_right_subtree_parent(inorder_index):
while right_subtree_parent_candidates and right_subtree_parent_candidates[-1].val == inorder[inorder_index]:
parent_node = right_subtree_parent_candidates.pop()
inorder_index += 1
return parent_node, inorder_index
def attach_left_child(parent_node, child_node):
parent_node.left = child_node
right_subtree_parent_candidates.append(child_node)
def attach_right_child(parent_node, child_node):
parent_node.right = child_node
right_subtree_parent_candidates.append(child_node)
for node_value in preorder[1:]:
parent_node = right_subtree_parent_candidates[-1]
child_node = TreeNode(node_value)
if parent_node.val != inorder[inorder_index]:
attach_left_child(parent_node, child_node)
else:
parent_node, inorder_index = find_right_subtree_parent(inorder_index)
attach_right_child(parent_node, child_node)
return root |
||
|
|
||
| ## Step3 | ||
| - 典型コメント集: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.1rv0z8fm6lc3 | ||
|
|
||
| - https://github.com/goto-untrapped/Arai60/pull/53 | ||
| - Java | ||
| - 思いつかない時は小さい例から考えてみる、遅くても良いから動くものを作ってみる | ||
|
|
||
| - https://github.com/kazukiii/leetcode/pull/30 | ||
| - C++ | ||
| - 再帰DFS、iterativeな方法2種類(コメント) | ||
| - iterativeな方法では、preorderを基準に回していくパターンが多い | ||
| - `current_index`: preorderのindex、 `inorder_position`: inorderでのインデックス、 `right_limit`: その節の subtree が inorder 上で越えてはいけない右端としたとき、 | ||
| - `node_position` < `current_index` | ||
| - → current の左子 | ||
| - `current_index` < `node_position` < `right_limit` | ||
| - → current の右子 | ||
| - `node_position` >= `right_limit` | ||
| - → current の subtree には入らないので pop して祖先へ戻る | ||
| - https://github.com/kazukiii/leetcode/pull/30/changes#r1821628634 | ||
| - inorderを基準にして構築する方法。あまり理解できず | ||
| - cf. https://github.com/fuga-98/arai60/pull/29#discussion_r2020242408 | ||
| - cf. https://github.com/tarinaihitori/leetcode/pull/29#discussion_r2044913447 | ||
|
|
||
| - https://github.com/nittoco/leetcode/pull/37 | ||
| - preorderでの構築 | ||
| - cf. https://github.com/nittoco/leetcode/pull/37#discussion_r1821720967 | ||
| - cf. https://github.com/nittoco/leetcode/pull/37#discussion_r1831463376 | ||
|
|
||
| - https://discord.com/channels/1084280443945353267/1478763507963924522/1492871022511390843 | ||
| - inorder を並び順、preorder の index を優先度として Cartesian tree を作っている | ||
| - Cartesian tree: 「配列上の順番」と「優先度」の2つの条件を同時に満たす二分木 | ||
|
|
||
| - コメントを読んでいても、iterativeな方法はMediumにしてはけっこう難しいように思える。再帰アプローチなら理解してしまえばMediumレベルな気がする | ||
|
|
||
| ## Step3 | ||
| ### 再帰DFS | ||
| ```py | ||
| class Solution: | ||
| def buildTree(self, preorder, inorder): | ||
| in_pos = {value: i for i, value in enumerate(inorder)} | ||
|
|
||
| def build_nodes(pre_l, pre_r, in_l, in_r): | ||
| if pre_l > pre_r: | ||
| return None | ||
|
|
||
| root_val = preorder[pre_l] | ||
| root = TreeNode(root_val) | ||
|
|
||
| mid = in_pos[root_val] | ||
| left_size = mid - in_l | ||
|
|
||
| root.left = build_nodes(pre_l + 1, pre_l + left_size, in_l, mid - 1) | ||
| root.right = build_nodes(pre_l + left_size + 1, pre_r, mid + 1, in_r) | ||
|
|
||
| return root | ||
|
|
||
| return build_nodes(0, len(preorder) - 1, 0, len(inorder) - 1) | ||
| ``` | ||
| - 所要時間: | ||
| - 1回目: 4:50 | ||
| - 2回目: 5:34 | ||
| - 3回目: 4:32 | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
言いたいことはわからなくもないですが、定義はしっかりと書いたほうが良いと思います。
特に、今回のアプローチはpreorderとinorderの性質を用いているので、なおさらです。
例えば、以下のサイトのように、定義と例を書いておいてもいいと思います。
https://www.geeksforgeeks.org/dsa/inorder-traversal-of-binary-tree/
https://www.geeksforgeeks.org/dsa/preorder-traversal-of-binary-tree/