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
165 changes: 165 additions & 0 deletions memo.md
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` の順序

@h-masder h-masder Jul 5, 2026

Copy link
Copy Markdown

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/

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

変数名は略さずに書いたほうが良いかなと思いました。また、midについては、rootのinorder indexという意味のほうが良いと思いました。

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)

@h-masder h-masder Jul 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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版

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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