Builds on Bottom-Up DFS and Hash Map, which you haven’t finished yet.
Tree Construction (Build from Preorder + Inorder)
A binary tree whose values are all different was walked twice, and each walk wrote its values into a list.
preorderlists every subtree as its root first, then its left subtree, then its right subtree.inorderlists every subtree as its left subtree first, then its root, then its right subtree.
Both lists describe the same tree and hold the same values. Rebuild that tree and return its root.
Construct Binary Tree
| 1 | from typing import Optional |
| 2 | Â |
| 3 | class TreeNode: |
| 4 | def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None): |
| 5 | self.val = val |
| 6 | self.left = left |
| 7 | self.right = right |
| 8 | Â |
| 9 | def buildTree(preorder: list[int], inorder: list[int]) -> Optional[TreeNode]: |
| 10 | """Construct Binary Tree from Preorder and Inorder Traversal: preorder names each root, inorder splits it. |
| 11 | Time: O(N) | Space: O(N) index map + O(H) recursion stack |
| 12 | """ |
| 13 | where = {val: i for i, val in enumerate(inorder)} # Value -> its index in inorder, for O(1) splits |
| 14 | pre_idx = 0 # preorder[pre_idx] is the root of the next subtree to build |
| 15 | Â |
| 16 | def build(lo: int, hi: int) -> Optional[TreeNode]: |
| 17 | """Build the subtree whose values are inorder[lo..hi].""" |
| 18 | nonlocal pre_idx |
| 19 | if lo > hi: |
| 20 | return None # Empty range: no subtree here |
| 21 | root = TreeNode(preorder[pre_idx]) # Preorder lists every root before its subtrees |
| 22 | pre_idx += 1 |
| 23 | mid = where[root.val] # inorder[lo..mid-1] is the left subtree, inorder[mid+1..hi] the right one |
| 24 | root.left = build(lo, mid - 1) # Left first: preorder lists the whole left subtree next |
| 25 | root.right = build(mid + 1, hi) |
| 26 | return root |
| 27 | Â |
| 28 | return build(0, len(inorder) - 1) |
build(lo, hi) builds exactly the values in inorder[lo..hi], and when it starts, preorder[pre_idx] is the root of that range.Preorder writes every subtree as its root first, then its whole left subtree, then its whole right subtree, so the next unused value, preorder[pre_idx], is always the root of the subtree you are building. Inorder puts that root between its two subtrees, so mid = where[root.val] splits the range: inorder[lo..mid-1] is the left subtree and inorder[mid+1..hi] the right one. This is correct because the values are distinct, so mid is one exact index, and each call to build uses exactly one preorder value per node of its range; when build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree and points at the right subtree's root. The trap is the order of the two calls: root.left = build(lo, mid - 1) must run before root.right = build(mid + 1, hi), because pre_idx hands out roots in preorder; build the right side first and it takes the left subtree's root.
See it run
Building a tree from preorder and inorder, step by step
pre_idx 0range inorder[0..4]
Each circle is a tree node, drawn above its position in inorder. A node appears when its call of build creates it. The rows show preorder and inorder.
rootthe value preorder[pre_idx] just used as the root of this callrangethe part of inorder that this call must buildbuilta node that already exists, or a preorder value already used
Start with pre_idx = 0 and the whole inorder range [0..4]. No node exists yet.
Gotcha 1 of 4
- The trap
Gotcha 1: Building the right subtree before the left one
Mistakeroot.right = build(mid + 1, hi) root.left = build(lo, mid - 1)Fixroot.left = build(lo, mid - 1) root.right = build(mid + 1, hi)What happens:
pre_idxhands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the next root afterrootbelongs to the left subtree. If the right side is built first, it takes that root. Onpreorder = [3,9,20,15,7], the right subtree gets 9. The later calls then read past the end ofpreorder, and Python raises an IndexError.Example: Construct Binary Tree from Preorder and Inorder Traversal (LC 105): preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
- You get only one list, the preorder of a binary search tree, as in Construct Binary Search Tree from Preorder Traversal (LC 1008): the BST order takes the place of
inorder, so each value is placed by the(low, high)window from its ancestors, not split by its index in a second list. See BST Bounds
Deep Dive
Why preorder names the root and inorder splits it
Preorder visits a node first, then its whole left subtree, then its whole right subtree. So the first value of any subtree is its root. Inside one call of build, the next unused value preorder[pre_idx] is the root of the subtree that call must build.
Inorder visits the left subtree, then the node, then the right subtree. So the root sits between its two subtrees. Because all values are distinct, mid = where[root.val] is one exact index. The values inorder[lo..mid-1] are the left subtree, and the values inorder[mid+1..hi] are the right subtree.
Each call uses exactly one value of preorder for each node in its range. When build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree. It now points at the root of the right subtree, which is what build(mid + 1, hi) needs.
Example: in Construct Binary Tree from Preorder and Inorder Traversal (LC 105), preorder = [3, 9, 20, 15, 7] and inorder = [9, 3, 15, 20, 7] give the tree [3, 9, 20, null, null, 15, 7]. The root is 3. Inorder has [9] on its left and [15, 20, 7] on its right.
One trace: the calls and pre_idx
The board replays the example. Each node is drawn above its index in inorder, so the range of a subtree is always a block of neighbours. A node appears when its call creates it.
build(0, 4) uses preorder[0] = 3. Its index in inorder is 1, so the left range is [0..0] and the right range is [2..4]. The left call uses preorder[1] = 9, whose range [0..0] splits into two empty ranges. Then pre_idx is 2.
The right call build(2, 4) now reads preorder[2] = 20. This is correct only because the left call has used exactly the values of its own subtree. Then 15 and 7 are the roots of the two ranges inside [2..4]. At the end, pre_idx is 5, which is the number of values.
For 5 nodes there are 11 calls: 5 calls create a node and 6 calls get an empty range. A tree with N nodes always has N calls that create a node and N + 1 calls with an empty range.
The trap: the order of the two calls
pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the left call must run first.
Suppose the right call runs first. For the example, build(0, 4) creates the root 3, and then build(2, 4) takes preorder[1] = 9. The value 9 is at inorder index 0, which is outside the range [2..4]. The call builds a wrong node, and the later calls read past the end of preorder. Python raises an IndexError.
pre_idx must also be one shared counter. If it is passed down as an argument, the right call starts right after its parent, as if the left subtree were empty. The call has no way to know how many values the left call used.
The empty test must be lo > hi. The range lo == hi has one value, which is a leaf. With lo >= hi, every leaf returns None and does not use its preorder value. On the example, the result is [3, null, 9, null, 20, 15], which is wrong.
The cost, and the neighbouring cards
The dictionary where is built once, so each split costs O(1). Each of the N values makes one node and two calls, so the time is O(N). The space is O(N) for where, plus O(H) for the recursion stack, where H is the height of the tree.
inorder.index(root.val) gives the same answer, but each call scans the list. For a tree shaped like a chain of 1000 nodes, the scans compare 500500 values in total, and the dictionary does 1000 lookups.
Inorder and postorder is the same idea, as in Construct Binary Tree from Inorder and Postorder Traversal (LC 106). Postorder writes the root last, so read it from the end, and build the right subtree first. Preorder and postorder, as in Construct Binary Tree from Preorder and Postorder Traversal (LC 889), do not fix one tree in general, so that problem has extra rules.
With one list only, the preorder of a binary search tree, the sorted order replaces inorder. Then the idea of the Bounds card applies, as in Construct Binary Search Tree from Preorder Traversal (LC 1008).
60-Second Pitch
"Preorder gives me the root of every subtree, and inorder tells me which values are on its left and which are on its right. I keep a map from each value to its inorder index. I keep one shared counter pre_idx into preorder. build(lo, hi) returns None for an empty range. Otherwise it takes preorder[pre_idx] as the root and moves the counter forward. It then finds mid from the map, builds the left range lo..mid-1 first, and the right range mid+1..hi second. The left call must run first, because preorder lists the whole left subtree before the right subtree. The time is O(N) with the map, and the space is O(N) plus the recursion stack of height H."
Common Questions
- Why must the left subtree be built before the right subtree?
Preorder writes the root, then the whole left subtree, then the whole right subtree. The shared counter
pre_idxmoves through preorder from left to right. After the root, the next values belong to the left subtree. If the right call ran first, it would take the left subtree's root.- Why do you need distinct values?
The map
wheregives one index for each value. With a repeated value, the root could match more than one place ininorder, so the split is not fixed. The problem promises distinct values, so each split is exact.- What are the time and space complexity?
O(N)time, because each value creates one node and makes two calls, and each split is a map lookup.O(N)space for the map, plus O(H) for the recursion stack. For a chain-shaped tree, H is N.- How do you change it for inorder and postorder?
Postorder writes the root last, so start
post_idxat the end and move it backwards. The right subtree comes just before the root in postorder, so build the right range first and the left range second. This is Construct Binary Tree from Inorder and Postorder Traversal (LC 106).- Can preorder and postorder give a unique tree?
Not in general. If a node has only one child, preorder and postorder do not tell whether it is the left child or the right child. That is why Construct Binary Tree from Preorder and Postorder Traversal (LC 889) accepts any valid answer.
SWE Reasoning
Spot it
- The input gives two traversal orders of one binary tree, and the task is to rebuild the tree.
- The values are distinct, so every value has one exact place in the inorder list.
- One of the orders names the root of each subtree (preorder first, postorder last), and inorder splits the left side from the right side.
- A recursive build works on a range of one list, and the other list is read with one shared counter.
- The wording says construct, rebuild or restore a tree from its traversals.
The invariant, step by step
wheremaps each value to its index ininorder. Because values are distinct, each value has one index.build(lo, hi)builds exactly the valuesinorder[lo..hi]. Iflo > hi, there is nothing to build, so it returnsNone.- When the call starts,
preorder[pre_idx]is the root of that range, because preorder lists a root before all of its subtree. - The call takes the root, moves
pre_idxforward, and findsmid = where[root.val]. The left subtree isinorder[lo..mid-1]and the right subtree isinorder[mid+1..hi]. - The left call runs first. It uses exactly the preorder values of the left subtree, so
pre_idxthen points at the root of the right subtree. - The right call runs second and does the same. Every value is used once, and the whole tree is built.
Traps
- The right call before the left call. It takes the left subtree's root. The result is a wrong tree or an IndexError.
pre_idxpassed as an argument. Each call needs to know how many values the other call used. Share one counter withnonlocal.lo >= hias the empty test. A range with one value is a leaf. It must use its preorder value.inorder.index(root.val)in every call. The answer is the same, but a chain-shaped tree costs O(N^2). Buildwhereonce.- Repeated values. With duplicates,
where[root.val]is not one exact index, and the tree is not fixed. The problem promises distinct values.
Practice
Builds on Bottom-Up DFS and Hash Map, which you haven’t finished yet.
Tree Construction (Build from Preorder + Inorder)
A binary tree whose values are all different was walked twice, and each walk wrote its values into a list.
preorderlists every subtree as its root first, then its left subtree, then its right subtree.inorderlists every subtree as its left subtree first, then its root, then its right subtree.
Both lists describe the same tree and hold the same values. Rebuild that tree and return its root.
Construct Binary Tree
| 1 | from typing import Optional |
| 2 | Â |
| 3 | class TreeNode: |
| 4 | def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None): |
| 5 | self.val = val |
| 6 | self.left = left |
| 7 | self.right = right |
| 8 | Â |
| 9 | def buildTree(preorder: list[int], inorder: list[int]) -> Optional[TreeNode]: |
| 10 | """Construct Binary Tree from Preorder and Inorder Traversal: preorder names each root, inorder splits it. |
| 11 | Time: O(N) | Space: O(N) index map + O(H) recursion stack |
| 12 | """ |
| 13 | where = {val: i for i, val in enumerate(inorder)} # Value -> its index in inorder, for O(1) splits |
| 14 | pre_idx = 0 # preorder[pre_idx] is the root of the next subtree to build |
| 15 | Â |
| 16 | def build(lo: int, hi: int) -> Optional[TreeNode]: |
| 17 | """Build the subtree whose values are inorder[lo..hi].""" |
| 18 | nonlocal pre_idx |
| 19 | if lo > hi: |
| 20 | return None # Empty range: no subtree here |
| 21 | root = TreeNode(preorder[pre_idx]) # Preorder lists every root before its subtrees |
| 22 | pre_idx += 1 |
| 23 | mid = where[root.val] # inorder[lo..mid-1] is the left subtree, inorder[mid+1..hi] the right one |
| 24 | root.left = build(lo, mid - 1) # Left first: preorder lists the whole left subtree next |
| 25 | root.right = build(mid + 1, hi) |
| 26 | return root |
| 27 | Â |
| 28 | return build(0, len(inorder) - 1) |
build(lo, hi) builds exactly the values in inorder[lo..hi], and when it starts, preorder[pre_idx] is the root of that range.Preorder writes every subtree as its root first, then its whole left subtree, then its whole right subtree, so the next unused value, preorder[pre_idx], is always the root of the subtree you are building. Inorder puts that root between its two subtrees, so mid = where[root.val] splits the range: inorder[lo..mid-1] is the left subtree and inorder[mid+1..hi] the right one. This is correct because the values are distinct, so mid is one exact index, and each call to build uses exactly one preorder value per node of its range; when build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree and points at the right subtree's root. The trap is the order of the two calls: root.left = build(lo, mid - 1) must run before root.right = build(mid + 1, hi), because pre_idx hands out roots in preorder; build the right side first and it takes the left subtree's root.
See it run
Building a tree from preorder and inorder, step by step
pre_idx 0range inorder[0..4]
Each circle is a tree node, drawn above its position in inorder. A node appears when its call of build creates it. The rows show preorder and inorder.
rootthe value preorder[pre_idx] just used as the root of this callrangethe part of inorder that this call must buildbuilta node that already exists, or a preorder value already used
Start with pre_idx = 0 and the whole inorder range [0..4]. No node exists yet.
Gotcha 1 of 4
- The trap
Gotcha 1: Building the right subtree before the left one
Mistakeroot.right = build(mid + 1, hi) root.left = build(lo, mid - 1)Fixroot.left = build(lo, mid - 1) root.right = build(mid + 1, hi)What happens:
pre_idxhands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the next root afterrootbelongs to the left subtree. If the right side is built first, it takes that root. Onpreorder = [3,9,20,15,7], the right subtree gets 9. The later calls then read past the end ofpreorder, and Python raises an IndexError.Example: Construct Binary Tree from Preorder and Inorder Traversal (LC 105): preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
- You get only one list, the preorder of a binary search tree, as in Construct Binary Search Tree from Preorder Traversal (LC 1008): the BST order takes the place of
inorder, so each value is placed by the(low, high)window from its ancestors, not split by its index in a second list. See BST Bounds
Deep Dive
Why preorder names the root and inorder splits it
Preorder visits a node first, then its whole left subtree, then its whole right subtree. So the first value of any subtree is its root. Inside one call of build, the next unused value preorder[pre_idx] is the root of the subtree that call must build.
Inorder visits the left subtree, then the node, then the right subtree. So the root sits between its two subtrees. Because all values are distinct, mid = where[root.val] is one exact index. The values inorder[lo..mid-1] are the left subtree, and the values inorder[mid+1..hi] are the right subtree.
Each call uses exactly one value of preorder for each node in its range. When build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree. It now points at the root of the right subtree, which is what build(mid + 1, hi) needs.
Example: in Construct Binary Tree from Preorder and Inorder Traversal (LC 105), preorder = [3, 9, 20, 15, 7] and inorder = [9, 3, 15, 20, 7] give the tree [3, 9, 20, null, null, 15, 7]. The root is 3. Inorder has [9] on its left and [15, 20, 7] on its right.
One trace: the calls and pre_idx
The board replays the example. Each node is drawn above its index in inorder, so the range of a subtree is always a block of neighbours. A node appears when its call creates it.
build(0, 4) uses preorder[0] = 3. Its index in inorder is 1, so the left range is [0..0] and the right range is [2..4]. The left call uses preorder[1] = 9, whose range [0..0] splits into two empty ranges. Then pre_idx is 2.
The right call build(2, 4) now reads preorder[2] = 20. This is correct only because the left call has used exactly the values of its own subtree. Then 15 and 7 are the roots of the two ranges inside [2..4]. At the end, pre_idx is 5, which is the number of values.
For 5 nodes there are 11 calls: 5 calls create a node and 6 calls get an empty range. A tree with N nodes always has N calls that create a node and N + 1 calls with an empty range.
The trap: the order of the two calls
pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the left call must run first.
Suppose the right call runs first. For the example, build(0, 4) creates the root 3, and then build(2, 4) takes preorder[1] = 9. The value 9 is at inorder index 0, which is outside the range [2..4]. The call builds a wrong node, and the later calls read past the end of preorder. Python raises an IndexError.
pre_idx must also be one shared counter. If it is passed down as an argument, the right call starts right after its parent, as if the left subtree were empty. The call has no way to know how many values the left call used.
The empty test must be lo > hi. The range lo == hi has one value, which is a leaf. With lo >= hi, every leaf returns None and does not use its preorder value. On the example, the result is [3, null, 9, null, 20, 15], which is wrong.
The cost, and the neighbouring cards
The dictionary where is built once, so each split costs O(1). Each of the N values makes one node and two calls, so the time is O(N). The space is O(N) for where, plus O(H) for the recursion stack, where H is the height of the tree.
inorder.index(root.val) gives the same answer, but each call scans the list. For a tree shaped like a chain of 1000 nodes, the scans compare 500500 values in total, and the dictionary does 1000 lookups.
Inorder and postorder is the same idea, as in Construct Binary Tree from Inorder and Postorder Traversal (LC 106). Postorder writes the root last, so read it from the end, and build the right subtree first. Preorder and postorder, as in Construct Binary Tree from Preorder and Postorder Traversal (LC 889), do not fix one tree in general, so that problem has extra rules.
With one list only, the preorder of a binary search tree, the sorted order replaces inorder. Then the idea of the Bounds card applies, as in Construct Binary Search Tree from Preorder Traversal (LC 1008).
60-Second Pitch
"Preorder gives me the root of every subtree, and inorder tells me which values are on its left and which are on its right. I keep a map from each value to its inorder index. I keep one shared counter pre_idx into preorder. build(lo, hi) returns None for an empty range. Otherwise it takes preorder[pre_idx] as the root and moves the counter forward. It then finds mid from the map, builds the left range lo..mid-1 first, and the right range mid+1..hi second. The left call must run first, because preorder lists the whole left subtree before the right subtree. The time is O(N) with the map, and the space is O(N) plus the recursion stack of height H."
Common Questions
- Why must the left subtree be built before the right subtree?
Preorder writes the root, then the whole left subtree, then the whole right subtree. The shared counter
pre_idxmoves through preorder from left to right. After the root, the next values belong to the left subtree. If the right call ran first, it would take the left subtree's root.- Why do you need distinct values?
The map
wheregives one index for each value. With a repeated value, the root could match more than one place ininorder, so the split is not fixed. The problem promises distinct values, so each split is exact.- What are the time and space complexity?
O(N)time, because each value creates one node and makes two calls, and each split is a map lookup.O(N)space for the map, plus O(H) for the recursion stack. For a chain-shaped tree, H is N.- How do you change it for inorder and postorder?
Postorder writes the root last, so start
post_idxat the end and move it backwards. The right subtree comes just before the root in postorder, so build the right range first and the left range second. This is Construct Binary Tree from Inorder and Postorder Traversal (LC 106).- Can preorder and postorder give a unique tree?
Not in general. If a node has only one child, preorder and postorder do not tell whether it is the left child or the right child. That is why Construct Binary Tree from Preorder and Postorder Traversal (LC 889) accepts any valid answer.
SWE Reasoning
Spot it
- The input gives two traversal orders of one binary tree, and the task is to rebuild the tree.
- The values are distinct, so every value has one exact place in the inorder list.
- One of the orders names the root of each subtree (preorder first, postorder last), and inorder splits the left side from the right side.
- A recursive build works on a range of one list, and the other list is read with one shared counter.
- The wording says construct, rebuild or restore a tree from its traversals.
The invariant, step by step
wheremaps each value to its index ininorder. Because values are distinct, each value has one index.build(lo, hi)builds exactly the valuesinorder[lo..hi]. Iflo > hi, there is nothing to build, so it returnsNone.- When the call starts,
preorder[pre_idx]is the root of that range, because preorder lists a root before all of its subtree. - The call takes the root, moves
pre_idxforward, and findsmid = where[root.val]. The left subtree isinorder[lo..mid-1]and the right subtree isinorder[mid+1..hi]. - The left call runs first. It uses exactly the preorder values of the left subtree, so
pre_idxthen points at the root of the right subtree. - The right call runs second and does the same. Every value is used once, and the whole tree is built.
Traps
- The right call before the left call. It takes the left subtree's root. The result is a wrong tree or an IndexError.
pre_idxpassed as an argument. Each call needs to know how many values the other call used. Share one counter withnonlocal.lo >= hias the empty test. A range with one value is a leaf. It must use its preorder value.inorder.index(root.val)in every call. The answer is the same, but a chain-shaped tree costs O(N^2). Buildwhereonce.- Repeated values. With duplicates,
where[root.val]is not one exact index, and the tree is not fixed. The problem promises distinct values.