translation: Update chapter_divide_and_conquer / build_binary_tree_pr… (#1653)

* translation: Update chapter_divide_and_conquer / build_binary_tree_problem.md

* Update build_binary_tree_problem.md

preorder-> pre-order
inorder -> in-order

* Update build_binary_tree_problem.md

* Update build_binary_tree_problem.md

* Update build_binary_tree_problem.md

* Update build_binary_tree_problem.md

* Update build_binary_tree_problem.md

* Update build_binary_tree_problem.md
This commit is contained in:
Phoenix Xie
2025-03-09 09:32:30 +11:00
committed by GitHub
parent 2fcdd499ea
commit 59bd843953

View File

@ -1,67 +1,67 @@
# Building binary tree problem
# Building a binary tree problem
!!! question
Given the pre-order traversal `preorder` and in-order traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the figure below).
Given the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence of a binary tree, construct the binary tree and return its root node. Assume there are no duplicate node values in the binary tree (as shown in the figure below).
![Example data for building a binary tree](build_binary_tree_problem.assets/build_tree_example.png)
### Determining if it is a divide and conquer problem
### Determining if it is a divide-and-conquer problem
The original problem of constructing a binary tree from `preorder` and `inorder` is a typical divide and conquer problem.
The original problem of building a binary tree from the `preorder` and the `inorder` sequences is a typical divide-and-conquer problem.
- **The problem can be decomposed**: From the perspective of divide and conquer, we can divide the original problem into two subproblems: building the left subtree and building the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still use the above division method, dividing it into smaller subtrees (subproblems), until the smallest subproblem (empty subtree) is reached.
- **The subproblems are independent**: The left and right subtrees are independent of each other, with no overlap. When building the left subtree, we only need to focus on the parts of the in-order and pre-order traversals that correspond to the left subtree. The same applies to the right subtree.
- **Solutions to subproblems can be combined**: Once the solutions for the left and right subtrees (solutions to subproblems) are obtained, we can link them to the root node to obtain the solution to the original problem.
- **The problem can be decomposed**: From the perspective of divide-and-conquer, we can divide the original problem into two subproblemsbuilding the left subtree and building the right subtreeplus one operation of initializing the root node. For each subtree (subproblem), we continue applying the same approach, partitioning it into smaller subtrees (subproblems), until reaching the smallest subproblem (an empty subtree).
- **The subproblems are independent**: The left and right subtrees do not overlap. When building the left subtree, we only need the segments of the in-order and pre-order traversals that correspond to the left subtree. The same approach applies to the right subtree.
- **Solutions to subproblems can be combined**: Once we have constructed the left and right subtrees (the subproblem solutions), we can attach them to the root node to obtain the solution to the original problem.
### How to divide the subtrees
Based on the above analysis, this problem can be solved using divide and conquer, **but how do we use the pre-order traversal `preorder` and in-order traversal `inorder` to divide the left and right subtrees?**
Based on the above analysis, this problem can be solved using divide-and-conquer. **However, how do we use the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence to divide the left and right subtrees?**
By definition, `preorder` and `inorder` can be divided into three parts.
By definition, both the `preorder` and `inorder` sequences can be divided into three parts:
- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`.
- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`.
- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 3 | 9 | 2 1 7 ]`.
- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 9 | 3 | 1 2 7 ]`.
Using the data in the figure above, we can obtain the division results as shown in the figure below.
Using the data from the preceding figure, we can follow the steps shown in the next figure to obtain the division results:
1. The first element 3 in the pre-order traversal is the value of the root node.
2. Find the index of the root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 1 2 7 ]`.
3. Based on the division results of `inorder`, it is easy to determine the number of nodes in the left and right subtrees as 1 and 3, respectively, thus dividing `preorder` into `[ 3 | 9 | 2 1 7 ]`.
2. Find the index of the root node 3 in the `inorder` sequence, and use this index to split `inorder` into `[ 9 | 3 1 2 7 ]`.
3. According to the split of the `inorder` sequence, it is straightforward to determine that the left and right subtrees contain 1 and 3 nodes, respectively, so we can split the `preorder` sequence into `[ 3 | 9 | 2 1 7 ]` accordingly.
![Dividing the subtrees in pre-order and in-order traversals](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png)
![Dividing the subtrees in pre-order and in-order traversals](build_binary_tree_problem.assets/build_tree_pre-order_in-order_division.png)
### Describing subtree intervals based on variables
### Describing subtree ranges based on variables
Based on the above division method, **we have now obtained the index intervals of the root, left subtree, and right subtree in `preorder` and `inorder`**. To describe these index intervals, we need the help of several pointer variables.
Based on the above division method, **we have now obtained the index ranges of the root, left subtree, and right subtree in the `preorder` and `inorder` sequences**. To describe these index ranges, we use several pointer variables.
- Let the index of the current tree's root node in `preorder` be denoted as $i$.
- Let the index of the current tree's root node in `inorder` be denoted as $m$.
- Let the index interval of the current tree in `inorder` be denoted as $[l, r]$.
- Let the index of the current tree's root node in the `preorder` sequence be denoted as $i$.
- Let the index of the current tree's root node in the `inorder` sequence be denoted as $m$.
- Let the index range of the current tree in the `inorder` sequence be denoted as $[l, r]$.
As shown in the table below, the above variables can represent the index of the root node in `preorder` as well as the index intervals of the subtrees in `inorder`.
As shown in the table below, these variables represent the root nodes index in the `preorder` sequence and the index ranges of the subtrees in the `inorder` sequence.
<p align="center"> Table <id> &nbsp; Indexes of the root node and subtrees in pre-order and in-order traversals </p>
| | Root node index in `preorder` | Subtree index interval in `inorder` |
| | Root node index in `preorder` | Subtree index range in `inorder` |
| ------------- | ----------------------------- | ----------------------------------- |
| Current tree | $i$ | $[l, r]$ |
| Left subtree | $i + 1$ | $[l, m-1]$ |
| Right subtree | $i + 1 + (m - l)$ | $[m+1, r]$ |
Please note, the meaning of $(m-l)$ in the right subtree root index is "the number of nodes in the left subtree", which is suggested to be understood in conjunction with the figure below.
Please note that $(m-l)$ in the right subtree root index represents "the number of nodes in the left subtree." It may help to consult the figure below for a clearer understanding.
![Indexes of the root node and left and right subtrees](build_binary_tree_problem.assets/build_tree_division_pointers.png)
### Code implementation
To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping of elements in `inorder` to their indexes:
To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping from elements in the `inorder` sequence to their indexes:
```src
[file]{build_tree}-[class]{}-[func]{build_tree}
```
The figure below shows the recursive process of building the binary tree, where each node is established during the "descending" process, and each edge (reference) is established during the "ascending" process.
The figure below shows the recursive process of building the binary tree. Each node is created during the "descending" phase of the recursion, and each edge (reference) is formed during the "ascending" phase.
=== "<1>"
![Recursive process of building a binary tree](build_binary_tree_problem.assets/built_tree_step1.png)
@ -90,10 +90,10 @@ The figure below shows the recursive process of building the binary tree, where
=== "<9>"
![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png)
Each recursive function's division results of `preorder` and `inorder` are shown in the figure below.
Each recursive function's division of the `preorder` and `inorder` sequences is illustrated in the figure below.
![Division results in each recursive function](build_binary_tree_problem.assets/built_tree_overall.png)
![Division in each recursive function](build_binary_tree_problem.assets/built_tree_overall.png)
Assuming the number of nodes in the tree is $n$, initializing each node (executing a recursive function `dfs()`) takes $O(1)$ time. **Thus, the overall time complexity is $O(n)$**.
Assuming the binary tree has $n$ nodes, initializing each node (calling the recursive function `dfs()`) takes $O(1)$ time. **Therefore, the overall time complexity is $O(n)$**.
The hash table stores the mapping of `inorder` elements to their indexes, with a space complexity of $O(n)$. In the worst case, when the binary tree degenerates into a linked list, the recursive depth reaches $n$, using $O(n)$ stack frame space. **Therefore, the overall space complexity is $O(n)$**.
Because the hash table stores the mapping from `inorder` elements to their indexes, it requires $O(n)$ space. In the worst case, if the binary tree degenerates into a linked list, the recursive depth can reach $n$, consuming $O(n)$ stack space. **Hence, the overall space complexity is $O(n)$**.