Skip to content

105. Construct Binary Tree from Preorder and Inorder Traversal #43

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
38 changes: 38 additions & 0 deletions 105/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Solve Time : 21:05

Time: O(N^2)
Space: O(N)
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0) {
Copy link

Choose a reason for hiding this comment

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

if (preorder.empty()) { のほうがシンプルだと思います。

return nullptr;
}
// preorder_first element is root
auto root = new TreeNode(preorder[0]);
if (preorder.size() == 1) {
return root;
}
// get index of root in inorder
int inorder_root_index;
Copy link

Choose a reason for hiding this comment

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

int inorder_root_index = std::distance(inorder.begin(), std::find(inorder.begin(), inorder.end(), preorder[0])); のほうがシンプルだと思います。

for (int i = 0; i < inorder.size(); ++i) {
if (inorder[i] == preorder[0]) {
inorder_root_index = i;
break;
}
}
// get left right tree node count
int left_nodes_count = inorder_root_index;
// split vectors and recursive to left right
vector<int> preorder_left(preorder.begin() + 1, preorder.begin() + 1 + left_nodes_count);

Choose a reason for hiding this comment

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

inorderのルートを起点に左右に分割しvectorに要素を詰め込んでいると思うのですが
毎回全要素をコピーするよりは左右の範囲を管理するインデックスを使った方がコピーコストが下がるのかなと思いました。

vector<int> inorder_left(inorder.begin(), inorder.begin() + left_nodes_count);
Copy link

Choose a reason for hiding this comment

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

C++20 からで私はあまり知らないですが、subrange 使う手はありますね。

    TreeNode* buildTree(std::vector<int>& preorder, std::vector<int>& inorder) {
        return buildTreeHelper(
            std::ranges::subrange(preorder.begin(), preorder.end()),
            std::ranges::subrange(inorder.begin(), inorder.end())
        );
    }
    TreeNode* buildTreeHelper(std::ranges::subrange preorder, std::ranges::subrange inorder) {

root->left = buildTree(preorder_left, inorder_left);

vector<int> preorder_right(preorder.begin() + left_nodes_count + 1, preorder.end());
vector<int> inorder_right(inorder.begin() + left_nodes_count + 1, inorder.end());
root->right = buildTree(preorder_right, inorder_right);
return root;
}
};
52 changes: 52 additions & 0 deletions 105/step2_1_1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Time: O(N)
Space: O(N)
https://github.com/kazukiii/leetcode/pull/30#discussion_r1821506570
のコードが直感的に理解しづらかったので理解のために書いた
大筋の処理としては、preorderの順番に処理、inorderの位置情報を利用。
inorderの、特定のノードを境に左部分木と右部分木に分かれる性質を活用する。

*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
map<int, int> value_to_inorder_position;
Copy link

Choose a reason for hiding this comment

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

個人的には value_to_inorder_index のほうが分かりやすいように思いました。

for (int i = 0; i < inorder.size(); i++) {
value_to_inorder_position[inorder[i]] = i;
}
TreeNode dummy = TreeNode();
stack<tuple<TreeNode*, int, int>> nodes_and_limits;
// 番兵の設置、left/rightのlimitはinorderにおける左右部分木の存在可能な限界を示す、番兵の初期値としてmaxを与える
nodes_and_limits.push({&dummy, numeric_limits<int>::max(), numeric_limits<int>::max()});
for (int p: preorder) {
Copy link

Choose a reason for hiding this comment

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

p のあとにスペースを空けることをお勧めします。

auto node = new TreeNode(p);
auto node_inorder_position = value_to_inorder_position[p];
auto [parent_node, parent_left_limit, parent_right_limit] = nodes_and_limits.top();
// inorderの位置が、暫定親親の持つ左部分木の限界よりも小さい場合、暫定親は親に確定、その左部分木にnodeを追加する
// preorderを順番に処理しているので、inorderにおける下記位置条件を満たすなら、常に直前の要素の左部分木のrootとなる
if (node_inorder_position < parent_left_limit) {
parent_node->left = node;
nodes_and_limits.push({node, node_inorder_position, parent_left_limit});
continue;
}
// 左部分木に関する条件を満たさないので、右部分木の始点を探す
while (!nodes_and_limits.empty()) {
// 現在処理中のnodeのinorderにおける位置が、暫定親の右部分木の限界よりも小さい場合、その暫定親の右部分木のrootとして追加する
auto [parent_node, parent_left_limit, parent_right_limit] = nodes_and_limits.top();
// 暫定親(stack末尾ノード)の右部分木の上限よりも小さいなら、そこが右部分木の始点となる
// ここに到達するまでに処理した暫定親は右部分木の限界がnodeの位置よりも左にあるため、右部分木の始点となれない
if (node_inorder_position < parent_right_limit) {
// 右部分木の親が確定したら右部分木を生やして新たな処理対応としてstackへpush
parent_node->right = node;
nodes_and_limits.pop();
nodes_and_limits.push({node, node_inorder_position, parent_right_limit});
break;
}
// 次の暫定親の確認のためpop
nodes_and_limits.pop();
}
}
return dummy.left;
}
};

44 changes: 44 additions & 0 deletions 105/step2_1_2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
2_1_1を理解するために番兵を除去したコード
left_limitは左部分木の右側の限界位置、right_limitが右部分木の右側の限界位置をそれぞれ示す
それがstackに積んだrootを含むtupleから直感的に理解できる
forの中にwhileと捉えるとわかりづらくなるが、シンプルにpreorder traversalと捉えるとわかりやすくなる
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
map<int, int> value_to_inorder_position;
for (int i = 0; i < inorder.size(); i++) {
value_to_inorder_position[inorder[i]] = i;
}
auto root = new TreeNode(preorder[0]);
stack<tuple<TreeNode*, int, int>> nodes_and_limits;
nodes_and_limits.push({root, value_to_inorder_position[root->val], preorder.size()});
for (int i = 1; i < preorder.size(); ++i) {
auto p = preorder[i];
auto node = new TreeNode(p);
auto node_inorder_index = value_to_inorder_position[node->val];
auto [parent_node, parent_left_limit, parent_right_limit] = nodes_and_limits.top();
if (node_inorder_index < parent_left_limit) {
parent_node->left = node;
nodes_and_limits.push({node, node_inorder_index, parent_left_limit});
continue;
}
while (!nodes_and_limits.empty()) {
auto [parent_node, parent_left_limit, parent_right_limit] = nodes_and_limits.top();
if (node_inorder_index < parent_right_limit) {
nodes_and_limits.pop();
parent_node->right = node;
// 左部分木のrootをstackにpushする時に、right_limitが親のleft_limitになるのは、親の位置が右部分木の限界位置だから
// 右部分木のrootをstackにpushする時、親のright_limitがそのまま使われるのは、親の位置によって右部分木の限界位置が定まらないから
nodes_and_limits.push({node, node_inorder_index, parent_right_limit});
break;
}
nodes_and_limits.pop();
}
}

return root;
}
};

41 changes: 41 additions & 0 deletions 105/step2_2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
https://github.com/kazukiii/leetcode/pull/30#discussion_r1821628634
を理解するための練習
inorder traversalの動きに合わせて構築も同時にしていると捉えるとわかりやすかった
大筋としては、inorder順の処理、preorderの位置情報、の二点を活用することで構築を行っている。
inorderの順番で処理しているので、特定のノードの処理が完了すると、そのノードをrootとする部分木の構築が完了している。

*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
map<int, int> value_to_preorder_position;
for (int i = 0; i < preorder.size(); ++i) {
value_to_preorder_position[preorder[i]] = i;
}
stack<TreeNode*> nodes;
//
auto gather_descendants = [&](int node_position) {
Copy link

Choose a reason for hiding this comment

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

node_position が preorder の index か inorder の index か分かりづらく感じました。 preorder_index はいかがでしょうか?

TreeNode* child = nullptr;
while (!nodes.empty()) {
auto back = nodes.top();
// stackの
if (value_to_preorder_position[back->val] < node_position) {
break;
}
nodes.pop();
back->right = child;
child = back;
}
return child;
};
for (int i: inorder) {
auto node = new TreeNode(i);
int node_position = value_to_preorder_position[node->val];
// inorder順に処理している
node->left = gather_descendants(node_position);
nodes.push(node);
}
return gather_descendants(-1);
}
};
29 changes: 29 additions & 0 deletions 105/step2_3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
step1をインデックスを使用して高速化するパターン
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {

Copy link

Choose a reason for hiding this comment

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

buildTreeWithIndex() を呼び出しているだけのため、空行は消してしまってよいと思います。


return buildTreeWithIndex(0, 0, preorder.size(), preorder, inorder);
}

TreeNode* buildTreeWithIndex(int preorder_root_index, int inorder_left, int inorder_right, vector<int>& preorder, vector<int>& inorder) {

Choose a reason for hiding this comment

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

これはpublicでなくてもいいかなと思いました。

Choose a reason for hiding this comment

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

preorderとinorderにどちらもconstをつけると、関数内では変更しないことをこの行で伝えられると思いました。

if (inorder_left >= inorder_right) {
return nullptr;
}
auto root = new TreeNode(preorder[preorder_root_index]);

int root_index;
for (int i = inorder_left; i < inorder_right; ++i) {
if (inorder[i] == root->val) {
root_index = i;
break;
}
}
root->left = buildTreeWithIndex(preorder_root_index + 1, inorder_left, root_index, preorder, inorder);
root->right = buildTreeWithIndex(preorder_root_index + 1 + (root_index - inorder_left), root_index + 1, inorder_right, preorder, inorder);

Choose a reason for hiding this comment

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

一つ目の引数ですが、ChatGptを使って理解できた(?)レベルでした。
コメントがあってもいいかもです🙇

return root;
}
};
26 changes: 26 additions & 0 deletions 105/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0) {
return nullptr;
}
auto root = new TreeNode(preorder[0]);
int inorder_root_index;
for (int i = 0; i < inorder.size(); ++i) {
if (inorder[i] == root->val) {
inorder_root_index = i;
break;
}
}

vector<int> left_preorder(preorder.begin() + 1, preorder.begin() + 1 + inorder_root_index);
vector<int> left_inorder(inorder.begin(), inorder.begin() + inorder_root_index);
root->left = buildTree(left_preorder, left_inorder);

vector<int> right_preorder(preorder.begin() + 1 + inorder_root_index, preorder.end());
vector<int> right_inorder(inorder.begin() + 1 + inorder_root_index, inorder.end());
root->right = buildTree(right_preorder, right_inorder);

Choose a reason for hiding this comment

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

preorder.begin() + 1 + inorder_root_index や inorder.begin() + inorder_root_index を変数としておくと区間をある境で分けていることが明確になる気もしました。+1 は末尾に書きたいと思いましたが趣味の範囲かもしれません。

return root;
}
};