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
399 changes: 399 additions & 0 deletions Construct Binary Tree from preorder and inorder traversal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
### Step1

- search_parent_of_right_childは入力のcandidatesの要素を変更してしまうが、これはまあしょうがない気がする(実際候補は変更するし、、、)
- ちょっと遅めだったが、nodeからindexの連想配列とか作ると速くなるかなあと何となく思う
- メモリは余分にかかるけどね
- search_parent_of_right_childのprev_nodeというのはちょっとスッキリしないが代案はない

```python
python
class Solution:
def is_inorder(self, node, next_node, inorder):
Copy link

Choose a reason for hiding this comment

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

is_inorder という関数名から、引数が inorder というものなのかどうかを判定する関数のように感じました。良いアイデアがないのですが、もう少し分かりやすい名前を付けたほうがよいと思います。

for i in range(len(inorder)):
if inorder[i] == node.val:
node_index = i
if inorder[i] == next_node.val:
next_node_index = i
return node_index < next_node_index

def search_parent_of_right_child(self, child_node, candidates, inorder):
prev_node = None
while True:
if not candidates:
return prev_node
node_searched = candidates.pop()
if not self.is_inorder(node_searched, child_node, inorder):
candidates.append(node_searched)
Comment on lines +24 to +26
Copy link

Choose a reason for hiding this comment

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

pop して 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.

そうですね、以前もsetで2回入れてる(結果的にsetだからOK)のが気になるというレビューをいただいたことがあり、冗長な操作をうっかりしてしまう癖がありそうなので気をつけます

return prev_node
prev_node = node_searched

def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
assert len(preorder) == len(inorder)
assert len(preorder) == len(set(preorder))
assert len(inorder) == len(set(inorder))
root = TreeNode(preorder[0])
parent_candidates = [root]
for i in range(1, len(preorder)):
child_node = TreeNode(preorder[i])
if not self.is_inorder(parent_candidates[-1], child_node, inorder):
parent_node = parent_candidates[-1]
parent_node.left = child_node
parent_candidates.append(child_node)
continue
parent_node = self.search_parent_of_right_child(child_node, parent_candidates, inorder)
parent_node.right = child_node
parent_candidates.append(child_node)
return root
```
Copy link

Choose a reason for hiding this comment

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

https://discord.com/channels/1084280443945353267/1247673286503039020/1300957769477918791
私が前に考えていたのは、リンク先のようなものなのですが、たしかに、スタックの一つ上をみると、範囲が分かるので、範囲情報をスタックに足す必要はないかもしれないですね。

Copy link

Choose a reason for hiding this comment

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

範囲情報をスタックから消してみた感想としては、結構非直感的になると思いました。

あるノードの left, right の下にぶら下げるかどうかの条件が、left の場合は、ぶら下げる先のノードの inorder における位置、right の場合は、ぶら下げる先のノードのスタック上の一つ上のノードの inorder における位置なのだけれども、これを分かりやすく説明するのが大変です。

速度の面では、こちらのほうの map や unordered_map のアクセスが増えて遅くなる要素が、あちらのほうの tuple が stack に載って遅くなる要素よりも、強く効くかしら。

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        map<int, int> inorder_position;
        for (int i = 0; i < inorder.size(); i++) {
            inorder_position[inorder[i]] = i;
        }
        // contains all nodes that may have a child.
        vector<TreeNode*> stack;
        TreeNode* root = new TreeNode(preorder[0]);
        stack.push_back(root);
        for (int i = 1; i < preorder.size(); i++) {
            TreeNode* node = new TreeNode(preorder[i]);
            int node_position = inorder_position[node->val];
            TreeNode* back = stack.back();
            if (node_position < inorder_position[back->val]) {
                back->left = node;
                stack.push_back(node);
                continue;
            }
            stack.pop_back();
            while (!stack.empty()) {
                TreeNode* parent = stack.back();
                if (node_position < inorder_position[parent->val]) {
                    break;
                }
                stack.pop_back();
                back = parent;
            }
            back->right = node;
            stack.push_back(node);
        }
        return root;
    }
};

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにleft_limit, right_limitの方が、やりたい操作に対して直接的な変数を定義してますね
操作の意図に合わせた変数の定義をするのも大事だなあと思いました。


## Step2

https://github.com/TORUS0818/leetcode/pull/31/files

[https://github.com/seal-azarashi/leetcode/pull/29](https://github.com/seal-azarashi/leetcode/pull/29#pullrequestreview-2344592484)

https://github.com/goto-untrapped/Arai60/pull/53

https://github.com/Ryotaro25/leetcode_first60/pull/31

https://github.com/hayashi-ay/leetcode/pull/43

- 再帰でやる方法は思いつかなかった

- 再帰で、子を呼び出す前に全部の処理を終える方法(ややこしいのでこうは書かないが練習のために)、さらにスライスでコピーをしないためにindexで管理
- .right, .leftの後処理さえしないために、引き継ぎに必要な情報は以下3つ
- 今のtree size
- 今のtreeで、preorderの最初のところ
- 今のtreeで、inorderの最初のところ
- 子を呼び出す前に処理するには、必要な情報は引数として渡す必要がある(returnでは渡せない)
- どこまで引数で渡し、どこまでreturnで渡すか、その選択の基準を自分の中でまだ持てていない
- listの[indexメソッド](https://docs.python.org/3/tutorial/datastructures.html)知らなかったのでドキュメント読んだ
- 複数ある場合は最初の値
- ない場合はValueError
- start, endのオプションがあり、その範囲内での相対indexを返す

```python
from dataclasses import dataclass

@dataclass
class SubtreeIndexRange:
subtree_size: int
preorder_min_index: int
inorder_min_index: int

class Solution:
def calculate_child_index_range(self, parent_subtree: SubtreeIndexRange, parent_index_inorder: int) -> tuple:
left_tree_size = parent_index_inorder - parent_subtree.inorder_min_index
left_preorder_min_index = parent_subtree.preorder_min_index + 1
left_inorder_min_index = parent_subtree.inorder_min_index
left_subtree = SubtreeIndexRange(left_tree_size, left_preorder_min_index, left_inorder_min_index)
right_tree_size = parent_subtree.subtree_size - left_tree_size - 1
right_preorder_min_index = parent_subtree.preorder_min_index + left_tree_size + 1
right_inorder_min_index = parent_subtree.inorder_min_index + left_tree_size + 1
right_subtree = SubtreeIndexRange(right_tree_size, right_preorder_min_index, right_inorder_min_index)
return left_subtree, right_subtree

def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:

Choose a reason for hiding this comment

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

必要以上に複雑な処理になっている印象を受けました。一度 inorder, preorder それぞれの配列の特徴と、最低限必要なデータが何かを整理して解法を考え直すと良いかもしれません。
手前味噌ですが、例えば私が同じ問題を解いたときの step 4 の最後の解答では、inorder の配列が頂点ノードが順に並んでいるという特徴から、これについては range を保持せず各イテレーションで一つずつカーソル (配列のポインタ) を進めるようにする等して、扱うデータを減らすように工夫しています: https://github.com/seal-azarashi/leetcode/pull/29/files

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の最終コードとコメントを見て理解できなかったのでまずは以下の疑問を解決したいのですが、
1, seal_azarashiさんのコードのCursorは何を指すのでしょうか。
2, inorder の配列が頂点ノードが順に並んでいるという特徴の「順」は何を差していますか

Choose a reason for hiding this comment

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

@nittoco
すいません返信が遅くなりました。

1, seal_azarashiさんのコードのCursorは何を指すのでしょうか。

preorder 配列のいずれかの要素を指すインデックスです。初期値が0で、buildTreeHelper() が呼ばれる度に値がインクリメント (一つ右に移動) されます。

2, inorder の配列が頂点ノードが順に並んでいるという特徴の「順」は何を差していますか

申し訳ありません、inorder と preorder を書き間違えていましたので訂正させてください。
「順」とは何かについてですが、これはツリーを preorder traversal した際の走査順序のことです。小さい画像で恐縮ですが、個人的に順序を示す図としてわかりやすかったものを添付しておきます:
Screenshot 2024-11-08 at 8 52 30

Copy link
Owner Author

@nittoco nittoco Nov 14, 2024

Choose a reason for hiding this comment

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

@seal-azarashi
すみません🙏、理解に時間がかかり遅くなりました。図の方までありがとうございます

  • L356, L357の順に再帰すると、結果的にpreorderの順に探索することになるから、関数内で+1(L353)すればいいということですね。コードとしては簡潔になりますが、このように再帰するとpreorderになる、ということは読み手側に推理させる必要はありそうですね。(慣れた人ならすぐわかりそうなのかが微妙ですが、同様のロジックのここではわかりにくいというコメントもありますね)
  • 上の自分のコードについては自分の書き方がわかりにくかったですが、気持ちとしては末尾再帰の(「node.left = 再帰関数」のように再帰関数のreturnを受け取った後の処理をするのは、させない)コードを書く練習をしたい、というのがありました。なので処理する情報は多くなりそうですが、今見てみるとそれにしても改善の余地はありそうですね。考え直してみます。

assert len(preorder) == len(inorder)
assert len(preorder) == len(set(preorder))
assert len(inorder) == len(set(inorder))
preorder_index_to_node = []
for val in preorder:
preorder_index_to_node.append(TreeNode(val))
node_val_to_inorder_index = {val: i for i, val in enumerate(inorder)}
def build_tree_helper(subtree: SubtreeIndexRange):
if subtree.subtree_size <= 1:
return None
parent_node = preorder_index_to_node[subtree.preorder_min_index]
parent_index_inorder = node_val_to_inorder_index[parent_node.val]
left_subtree, right_subtree = self.calculate_child_index_range(subtree, parent_index_inorder)
if left_subtree.subtree_size:
parent_node.left = preorder_index_to_node[left_subtree.preorder_min_index]
if right_subtree.subtree_size:
parent_node.right = preorder_index_to_node[right_subtree.preorder_min_index]
build_tree_helper(left_subtree)
build_tree_helper(right_subtree)
build_tree_helper(SubtreeIndexRange(len(preorder), 0, 0))
return preorder_index_to_node[0]

```

- dataclassのドキュメントと、ソースコード(https://github.com/python/cpython/blob/main/Lib/dataclasses.py)を読んだ
- process_class(L930)で__init__とか__repr__みたいな特殊メソッドを定義して、クラスを作っているみたい。主な関数はこんな感じ?
- def init_fn(L614)
- 関数の初期化。init_paramとfield_initが呼ばれている。前者はx:int=3のような__init__関数のパラメータ文字列、後者はself.x = 1のような__init__関数の中身の文字列が作られる。その後、add_fnを__init__関数で実行
- FuncBuilder.add_fn
- self.namesに関数名を、self.srcに関数のコードの文字列を入れる
- *FuncBuilder.*add_fns_to_class
- dataclassで作る、クラスの中の関数の一覧を返す関数(create_fn)を、文字列からexecで実行することで、name属性に関数を結びつける
- add_fnでsrcに関数一覧の中身のコードが入っているのでそれを中に置いて、return self.namesで関数名を返すことで、関数create_fnを作っている


https://github.com/fhiyo/leetcode/pull/31

- 再帰で呼び出す関数の引数に必要な計算を、あらかじめ別変数に入れてその変数を引数に、とやらずに、関数の引数の中で計算式を入れる選択肢がある。いつも忘れがち。
- 引数が何を示すかわかりにくくなるデメリットはあるかも?
- Step1のように、preorder順に見ていくが、rootからいちいち親を探す方法を実装
- いちいちrootに戻って探すのは手間な感じもするが、実装としては素直なのかもしれない
- 実行時間は結構かかる
- うーん、search_parent関数の中身ネストが深いが、代案が思いつかない

```python

class Solution:
def search_parent(self, child, root, node_val_to_inorder_index):

Choose a reason for hiding this comment

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

開発チームの方針や好みにもよりますが、buildTree() には型ヒントが付与されているので、この場合はこちらにも付与してあげると良いのではないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね、型ヒントに慣れていないのでLeetCodeのデフォルトの分だけつけてましたが、あった方が親切ですね

result = root
while True:
if node_val_to_inorder_index[child.val] == node_val_to_inorder_index[result.val]:
raise ValueError("The node is already in tree")
if node_val_to_inorder_index[child.val] < node_val_to_inorder_index[result.val]:
if not result.left:
return result, "left"
result = result.left
else:
if not result.right:
return result, "right"
result = result.right

def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
assert len(preorder) == len(inorder)
assert len(preorder) == len(set(preorder))
assert len(inorder) == len(set(inorder))
root = TreeNode(preorder[0])
node_val_to_inorder_index = {val: i for i, val in enumerate(inorder)}
for i in range(1, len(preorder)):
node = TreeNode(preorder[i])
parent, direction = self.search_parent(node, root, node_val_to_inorder_index)
if direction == "left":
parent.left = node
else:
parent.right = node
Comment on lines +167 to +170
Copy link

Choose a reason for hiding this comment

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

これ、search_parent の中に入れてしまってもいい気がします。

return root
```

- 再帰をstackに直す
- nullのnodeも突っ込んで後で取り出す時にチェックするより、どうせ.left, .rightで繋げる時に本当にあるかチェックしなきゃいけないので、stackにpushするかもその中に入れれば、取り出す時のチェックはいらない

```python

class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
assert len(preorder) == len(inorder)
assert len(preorder) == len(set(preorder))
assert len(inorder) == len(set(inorder))
Comment on lines +181 to +183

Choose a reason for hiding this comment

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

Step 2 の中で、このような不正な値のチェックがあったりなかったりするのが気になりました。どういった意図でチェックする or しないようにしているのか知りたいです!

Copy link
Owner Author

Choose a reason for hiding this comment

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

すみません、最後の方は単純に書き忘れてただけです。
自分の方針としては全部書きたいなと思います

root = TreeNode(preorder[0])
stack = [(root, preorder, inorder)]

Choose a reason for hiding this comment

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

Tuple だと各要素がどのような役割を持っているのか理解するのが少し大変なので、個人的には別のデータ構造を使ってほしい気持ちになりました。この場合そのままイミュータブルではあって欲しいので、NamedTuple や frozen 属性が付与されたdataclass あたりだと嬉しいです。特に後者は型ヒントも付与出来るので、実際のプロダクトで使われているとより安心感が強いですね。
あと、どのデータ構造を使うにしても、役割について一行程度のコメントが残っているとより親切に思います。

while stack:
parent, node_vals_preorder, node_vals_inorder = stack.pop()
left_count = node_vals_inorder.index(parent.val)
right_count = len(node_vals_preorder) - left_count - 1
if left_count:
parent.left = TreeNode(node_vals_preorder[1])
stack.append(
(
parent.left,
node_vals_preorder[1 : left_count + 1],
node_vals_inorder[:left_count],
Comment on lines +195 to +196
Copy link

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.

自分もStackで配列を丸ごと持つのはちょっと気になりました。範囲のindexを持つ方がメモリ使用量的には良いですかね、とはいえ配列で操作する方が処理は簡単に書けそうなのと、stackには多くてもlog(n)個しか積まれないはずなので(あってる?)許容範囲内かなとは思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね、Index持つのだと実装が複雑なので、改善するならnumpyとか使ってviewにするかという感じですかね
https://discord.com/channels/1084280443945353267/1251052599294296114/1265343878072897557

)
)
if right_count:
parent.right = TreeNode(node_vals_preorder[-right_count])
stack.append(
(
parent.right,
node_vals_preorder[-right_count:],
node_vals_inorder[-right_count:],
)
)
return root
```

- 上と同じロジックの再帰

```python

class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
def build_tree_helper(parent, node_val_preorder, node_val_inorder):
left_node_count = node_val_inorder.index(parent.val)
right_node_count = len(node_val_preorder) - left_node_count - 1
if left_node_count:
parent.left = TreeNode(node_val_preorder[1])
build_tree_helper(parent.left, node_val_preorder[1:left_node_count + 1], node_val_inorder[:left_node_count])
if right_node_count:
parent.right = TreeNode(node_val_preorder[-right_node_count])
build_tree_helper(parent.right, node_val_preorder[-right_node_count:], node_val_inorder[-right_node_count:])
root = TreeNode(preorder[0])
build_tree_helper(root, preorder, inorder)
return root
```

- nodeを返して、nodeを繋げるのは親がやることにした再帰
- これが簡潔で一番好き
- if left_node_count: の条件はつけず、一番上でif not preorder: returnをやるのは、stackのノードに空のも突っ込んで後でチェックするのに対応するのか(今更気づいた)

```python

class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
root = TreeNode(preorder[0])
left_node_count = inorder.index(root.val)
right_node_count = len(preorder) - left_node_count - 1
if left_node_count:
root.left = self.buildTree(preorder[1:left_node_count + 1], inorder[:left_node_count])
if right_node_count:
root.right = self.buildTree(preorder[-right_node_count:], inorder[-right_node_count:])

Choose a reason for hiding this comment

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

ただの感想ですが、Python だと negative index で指定が出来るのですね。勉強になりました。
調べたら JavaScript でも、配列操作では使えないけど slice() メソッドでは使えるみたいですね (知らなかった): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

return root
```

## Step4(追加のやり方)
- [この辺](https://discord.com/channels/1084280443945353267/1247673286503039020/1300957769477918791) を参照
- inorder順に見ていく方法
- 最初わけわかんなくなった(下が訳わかんなくなってエラーが出たコード)
- 各ループの時点で、stackにどこのnodeがどの順序で入っていれば良いのか混乱していた

```python
# 訳わかんなくなってエラーが出たコード

# inorer順に見てく書き方
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 入力値のチェック 今回は省略
stack = []

# inorderで今までのnodeの中で、.rightがまだのものを繋げる関数
def connect_right_child_nodes_so_far(node):
child = None
while stack:
parent = stack[-1]
if parent == node:
return child
parent.right = child
child = stack.pop()

node_val_to_preorder_index = {val: i for i, val in enumerate(preorder)}
prev_val = None
dummy = TreeNode()
root = None
for val in inorder:
node = TreeNode(val)
stack.append(node)
if prev_val is None:
prev_val = val
continue
if node_val_to_preorder_index[prev_val] < node_val_to_preorder_index[val]:
prev_val = val
continue
if node_val_to_preorder_index[val] == 0:
root = node
dummy.left = root
node.left = connect_right_child_nodes_so_far(node)
prev_val = val
return connect_right_child_nodes_so_far(dummy)
```

- かなり時間がかかったが、なんとか書けた、苦労した
- stackに入っているデータの不変条件をよく意識
- う〜ん、スッキリするような、しないような???

```python
class Solution:
dummy_root_preorder_index = -1
dummy_root_val = 0
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
stack = []
node_val_to_preorder_index = {val: i for i, val in enumerate(preorder)}
dummy_root = None
inorder.append(self.dummy_root_val)
node_val_to_preorder_index[None] = self.dummy_root_preorder_index

def construct_left_child_tree(node):
child = None
while stack:
parent = stack.pop()
parent.right = child
if node_val_to_preorder_index[parent.val] == node_val_to_preorder_index[node.val] + 1:
return parent
child = parent

prev_val = None
for val in inorder:
node = TreeNode(val)
if node_val_to_preorder_index[node.val] == -1:
dummy_root = node
if prev_val is not None and node_val_to_preorder_index[node.val] < node_val_to_preorder_index[prev_val]:
node.left = construct_left_child_tree(node)
stack.append(node)
prev_val = val
return dummy_root.left
```

- odaさんのコードを見た。construct_left_treeの中でstackの後ろを見て、ダメだったら終わりにすれば、いちいちpreorder順をinorderのforループの中で調べなくていいのか

```python

class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
node_val_to_preorder_index = {val: i for i, val in enumerate(preorder)}
stack = []
inorder_plus_dummy = inorder + [None]
node_val_to_preorder_index[None] = -inf

def construct_left_tree(node):
child = None
while True:
parent = stack[-1] if stack else None
Copy link

Choose a reason for hiding this comment

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

ここは、

if not stack:
    return child

分離したほうが好みです。

この関数意味としては「preorder にしたときに node よりも後ろに来るすべての stack の中のノードを .right で繋いで返す」ですね。そうすると、条件を満たさなくなるまでフィルターする動作と linked list をつなぐ動作の合わさったコードになりそうです。

Copy link
Owner Author

@nittoco nittoco Nov 7, 2024

Choose a reason for hiding this comment

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

すみません、これは分離した方が好みというのは、その後の段落の文章が理由になってますか🙏(それぞれの文意はわかってるつもりですが繋がりが見えず)
後の段落の文章は関係なく、単に見やすいから分離しましょうという意図ならわかります。

Copy link

Choose a reason for hiding this comment

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

「条件を満たさなくなるまでフィルターする動作」を書く時、こう書きたいなという気持ちがあるので、上の言い方になりましたが、確かに通じない言い方です。

while stack:
    parent = stack[-1]
    if condition:
        break
    move_to_next
return child

でもいいかもしれません。

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の中身がなくなるまで(今まで見たnodeの中で、.rightで繋いとくべきものがもうない)というのも、条件を満たすものはこれ以上ないということで @oda さんのいうフィルター終了条件と捉えてましたが、わかりにくかったですかね。

if not parent or node_val_to_preorder_index[parent.val] < node_val_to_preorder_index[node.val]:
return child
parent.right = child
stack.pop()
child = parent

for val in inorder_plus_dummy:
node = TreeNode(val)
node.left = construct_left_tree(node)
stack.append(node)
dummy = stack[-1]
Copy link

Choose a reason for hiding this comment

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

None を番兵にしてもいいんですが、私は construct_left_tree の引数を node_val_to_preorder_index[node.val] にすることで -inf を入れればすべて回収されるようにしました。
https://discord.com/channels/1084280443945353267/1247673286503039020/1300957861614063616

return dummy.left
```

- preorder順に見てくやつ(範囲情報も入れる)も実装してみた。
- 最初どういうことか分かってなかったが、自分のpositionと、stackに入っている自分の先祖の中で、自分より右にある中で一番左のpositionを入れると判定できるという案か
- 後者が、Step1の自分の実装では、結果的に一個上のnodeのpositionになってる
- この考えの方が自然なのか??
- odaさんの実装とは違い、右につながるやつの親はstackに入れっぱ
Copy link

Choose a reason for hiding this comment

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

私の実装の気持ちは「stack に入っているものは、.right がまだ決まっていないもの」というつもりです。
https://discord.com/channels/1084280443945353267/1247673286503039020/1300957769477918791
.left がまだ決まっていないものは、別に変数をおいてもいいかもしれないけれども、stack[-1] なので、いいかなあと。

これは preorder 順に素直に構築していっていて、そのために必要な情報が何かを考えると出てきます。
途中まで構築していて preorder 順に作った新しいノードをどうするかを考えると
「一つ前につけたノード(.left 候補)」「ここまで作ってきて .right がまだ決まっていないノード(.right 候補)」のどれかにつけることになるはずです。preorder ということは一回でもそこはつけないと決まったらつきません。だからスタック管理で良くて、あとは、どこにつけるかを決めるのに最低限必要な情報をすべてのノードにもたせてやればいいです。

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 に入っているものは、.right がまだ決まっていないもの」「.left がまだ決まっていないものは、別に変数をおいてもいいかも」の辺で意図が結構分かってすっきりしました、ありがとうございます。


```python

from dataclasses import dataclass

@dataclass
class InorderPosition:
node: TreeNode
left_limit: int
right_limit: int

class Solution:
def search_parent_of_right_child(self, node: TreeNode, current_inorder_index: int, stack: List[InorderPosition]):
while stack:
if current_inorder_index < stack[-1].right_limit:
return stack[-1]
stack.pop()

def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
dummy = TreeNode()
stack = [InorderPosition(dummy, inf, inf)]
node_val_to_inorder_index = {val: i for i, val in enumerate(inorder)}
for val in preorder:
node = TreeNode(val)
current_inorder_index = node_val_to_inorder_index[val]
back = stack[-1]
if current_inorder_index < back.left_limit:
back.node.left = node
stack.append(InorderPosition(node, current_inorder_index, back.left_limit))
continue
back = self.search_parent_of_right_child(node, current_inorder_index, stack)
back.node.right = node
stack.append(InorderPosition(node, current_inorder_index, back.right_limit))
return dummy.left
```