Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions medium/105/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Step1

かかった時間:解けず

計算量:
ノード数をNとして、

時間計算量:O(N^2)

空間計算量:O(N)

```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not preorder or not inorder:
return None

val = preorder[0]
inorder_split_index = inorder.index(val)

left_tree_inorder = inorder[:inorder_split_index]
Copy link

Choose a reason for hiding this comment

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

スライスをつくるより、インデックスの範囲を表す整数を用いたほうが、コピーが発生せず、処理が軽くなると思います。

right_tree_inorder = inorder[inorder_split_index + 1:]

num_left_nodes = len(left_tree_inorder)
left_tree_preorder = preorder[1:1 + num_left_nodes]
right_tree_preorder = preorder[1 + num_left_nodes:]

return TreeNode(
val,
self.buildTree(left_tree_preorder, left_tree_inorder),
self.buildTree(right_tree_preorder,right_tree_inorder)
)
```
思考ログ:
- 時間切れ、よく分かっていない事が分かった
- 部分木の小さな問題にしていくのは頭にあったが、pre/inorderの配列をどう分配すれば良いか分からなくなった
- 普通に考えれば、以下の手順でやれば良いが、恐らくinorderの理解が緩かったため困惑したのだろう
- preorderとinorderから親探す(preorderは先頭、inorderは見つけた親ノードを検索する)
- inorderの親の左右のノード数が左/右部分木のノード数
- preorderは、親ノード、左部分木ノード達、右部分木ノード達、という構成なので、上記で求めたノード数情報をもとに切り取り
- あと、この問題に限った事ではないが、思考をギブアップするのが早い気がする(時間というより、色々試す手間を惜しんでいるというか)
- L21の条件はなんかイケテナイ感じがある
- ```preorder```と```inorder```は対応してるので、片方が空になることはないが、混乱しそう
- 先に両者の長さが一致することを```assert```しといて片方だけ確認すればいいか?
- あと再帰上限要注意(3000)

# Step2

講師役目線でのセルフツッコミポイント:
- inorderのindexサーチに関して、ハッシュマップの選択肢は思い浮かんでおいた方がいいか
- スライスじゃなくてインデックスで管理しようというのも一個
- step1の解法、```num_left_nodes```なくても```inorder_split_index```使えばいいよね

参考にした過去ログなど:
- https://github.com/Yoshiki-Iwasa/Arai60/pull/33
- https://github.com/kazukiii/leetcode/pull/30
- preorder&inorder情報を使って上から木を構築していく方法
- https://github.com/fhiyo/leetcode/pull/31
> inorderでのsubrootのindexを求めるところは、先にkeyがノードのval, valueがinorderでのindexである辞書を作っておけばO(1)でlookupできるが、ノードの数が高々3Kなので二分探索で十分速いだろうと思いそのままにしている。
- ここら辺抜けてた、うーん
- https://github.com/YukiMichishita/LeetCode/pull/12
- 個人的に整理されてて読みやすかった
- https://github.com/sakupan102/arai60-practice/pull/30
- https://github.com/Mike0121/LeetCode/pull/12
- https://github.com/shining-ai/leetcode/pull/29#pullrequestreview-1948764345
- https://github.com/hayashi-ay/leetcode/pull/43

DFS-Loop
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
Copy link

Choose a reason for hiding this comment

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

これちょっと読みにくかったです。理由はっきりと分からないんですが、append_next_candidatesが何にappendするのかが全体から探さないと分からないからかもしれないと思いました。
関数に分けてるのも個人的には無い方が読みやすいかなと思いました。目の移動が多いデメリットが勝ったのかもしれないです

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。
append_next_candidatesは名前を横着しましたかね。。
一方で、関数化は書く側としては頭が整理できてよかったのかなと感じています(というか一気に書き下すと、うまく書けなかった気がします)

def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
def split_inorder(inorder, split_index):
left_inorder = inorder[:split_index]
right_inorder = inorder[split_index + 1:]

return left_inorder, right_inorder

def split_preorder(preorder, num_left_nodes):
left_preorder = preorder[1:1 + num_left_nodes]
right_preorder = preorder[1 + num_left_nodes:]

return left_preorder, right_preorder

def append_next_candidates(node, preorder, inorder):
inorder_split_index = inorder.index(node.val)
left_inorder, right_inorder = split_inorder(inorder, inorder_split_index)
left_preorder, right_preorder = split_preorder(preorder, len(left_inorder))

assert len(left_preorder) == len(left_inorder)
assert len(right_preorder) == len(right_inorder)

if left_preorder:
node.left = TreeNode(left_preorder[0])
node_and_order_pairs.append((node.left, left_preorder, left_inorder))
if right_preorder:
node.right = TreeNode(right_preorder[0])
node_and_order_pairs.append((node.right, right_preorder, right_inorder))

root = TreeNode(preorder[0])
Copy link

Choose a reason for hiding this comment

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

Step1では空配列のケアをしていますが、こちらと1つ下の解法ではしていないですね

node_and_order_pairs = [(root, preorder, inorder)]

while node_and_order_pairs:
next_node, next_preorder, next_inorder = node_and_order_pairs.pop()
if not next_node:
continue

append_next_candidates(next_node, next_preorder, next_inorder)

return root
```
思考ログ:
- left/rightのコピペミスでデバッグにやや時間がかかった
- 関数の切り出しを意識してみた

preorderとinorderの情報を見てrootから順に構築する方法
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
inorder_value_to_index = {value: index for index, value in enumerate(inorder)}
def insert_next_node(root: TreeNode, value: int) -> None:
node = root
while True:
assert value != node.val
if inorder_value_to_index[value] < inorder_value_to_index[node.val]:
if node.left:
node = node.left
continue
else:
node.left = TreeNode(value)
break

if inorder_value_to_index[node.val] < inorder_value_to_index[value]:
if node.right:
node = node.right
continue
else:
node.right = TreeNode(value)
break

root = TreeNode(preorder[0])
for value in preorder[1:]:
insert_next_node(root, value)

return root
```
思考ログ:
- https://github.com/YukiMichishita/LeetCode/pull/12/files
> 任意のiとjについて、i < j ⇒ preorder[i]はpreorder[j]と同じ階層かより上の階層にある
> 任意のiとjについて、i < j ⇒ inorder[i]はinorder[j]よりも左側にある
- preorderが挿入の順番、inorderが木の形の情報を教えてくれると思えばいい
- preorderの順に挿入していく
- 現在作成済みの木をrootから辿っていく
- 挿入するnodeは現在地(node)より左に挿入するのか右に挿入するのか、inorderでチェック
- 挿入すべき方向に子供がいなかったら挿入して終わり、子供がいたらポインタを進めて、inorderチェック(以下繰り返し)
- https://github.com/fhiyo/leetcode/pull/31/files
> ここでアサーションが上手くいかなかったときにinorder_indexやnode.valを出力してあげてもよい気がしました。

スライスじゃなくインデックスで管理
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
inorder_value_to_index = {value: index for index, value in enumerate(inorder)}

preorder_index = 0
Copy link

Choose a reason for hiding this comment

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

個人的には、使った preorder_index の数を build_tree_helper が一緒に返す形にするかなあと思いました。

def build_tree_helper(inorder_left_bound: int, inorder_right_bound: int) -> TreeNode:

Choose a reason for hiding this comment

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

細かいですが、return type は Optional[TreeNode] ですかね。

nonlocal preorder_index
if inorder_left_bound >= inorder_right_bound:
return None

root_val = preorder[preorder_index]
root = TreeNode(root_val)
preorder_index += 1
root.left = build_tree_helper(
inorder_left_bound, inorder_value_to_index[root_val]
)
root.right = build_tree_helper(
inorder_value_to_index[root_val] + 1, inorder_right_bound
)

return root

return build_tree_helper(0, len(preorder))
```
思考ログ:
- preorder_indexを内部で1ずつ増やしていくのがやや分かりにくいか
Copy link

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.

このタイミングでpreorderのindexズラしていって問題ないんだっけ、と少し詰まりました。

Choose a reason for hiding this comment

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

自分も理解に時間がかかりました。preorderのindexのstartとendを渡してあげると良さそうです。


# Step3

かかった時間:4min

```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
if not preorder or not inorder:
return None

root_val = preorder[0]
inorder_split_index = inorder.index(root_val)

Choose a reason for hiding this comment

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

この時点で変数名をleft_subtree_sizeとして良いかと思いました。
そうすると例えば下で、preorder[1:1+left_subtree_size] となり、インデックス1から長さが左部分木のサイズと等しいだけ切り出していることが一目でわかります。

root = TreeNode(root_val)
root.left = self.buildTree(preorder[1:1+inorder_split_index], inorder[:inorder_split_index])
root.right = self.buildTree(preorder[1+inorder_split_index:], inorder[inorder_split_index + 1:])

return root
```
思考ログ:
- ```.index```で捜索したり、スライスしたりと効率的ではないのだけど、シンプルで分かりやすい気はする
- ```preorder```のスライスに使ってる```inorder_split_index```は不親切かも、冗長でも```num_left_subtree_node```とかに入れておいた方がいいかもしれない
Copy link

@Yoshiki-Iwasa Yoshiki-Iwasa Aug 21, 2024

Choose a reason for hiding this comment

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

````inorder_split_index```は不親切かも

自分もそう思いました。わざわざ感ありますが、inorderを左右に分割する関数定義してもよさそうです

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。

短く書けてリーズナブルな気がしたので敢えて手を加えなかったのですが、もう少し丁寧にやっても良かったですね。


# Step4

```python
```
思考ログ: