Problem overview
buildTree receives two lists, preorder and inorder, which are the traversals of the same binary tree. All values are different. The function must rebuild the tree and return its root. The lesson provides the TreeNode class: TreeNode(x) creates a node with val set to x and left and right set to None.
With preorder = [3,9,20,15,7] and inorder = [9,3,15,20,7], the tree has 3 at the root, 9 as its left child and 20 as its right child, and 20 has the children 15 and 7. Level by level, that is [3,9,20,null,null,15,7], the form the tests use to print a tree.
Approach: preorder names the roots, inorder splits the sides
Two facts do all the work:
- In preorder, a subtree's root comes first, then its whole left subtree, then its whole right subtree. Reading
preorderfrom left to right therefore gives the roots in the order the recursion needs them. - In inorder, the left subtree comes before the root and the right subtree after it. Once you know where the root sits in
inorder, everything to its left is the left subtree and everything to its right is the right subtree.
So the first value of preorder is the root, its position in inorder divides the remaining values into two groups, and each group is a smaller copy of the same problem. Every subtree occupies one unbroken stretch of inorder, so two indices, lo and hi, describe it.
The only costly part is finding a value's position in inorder. Searching the list each time would cost O(n) per node and O(n²) overall. Since the values are distinct, a dict from each value to its index answers in constant time, which gives O(n) overall.
Here is the recursion on the example. Each line is one call of build, with the stretch of inorder it covers:
build lo=0 hi=4 root=3
build lo=0 hi=0 root=9
build lo=0 hi=-1 empty
build lo=1 hi=0 empty
build lo=2 hi=4 root=20
build lo=2 hi=2 root=15
build lo=2 hi=1 empty
build lo=3 hi=2 empty
build lo=4 hi=4 root=7
build lo=4 hi=3 empty
build lo=5 hi=4 empty
The root 3 sits at index 1 of inorder, so the left call covers indices 0 to 0 and the right call covers 2 to 4. The next root in preorder is 9, and it has no children: both of its calls get an empty range. Then the right call takes 20 from preorder, which sits at index 3 of inorder, and the process repeats.
Step by step
- Create the lookup dict as
self.inorderIndex, an attribute, so the helper written later can read it too. - Loop over every index of
inorderwithrange(len(inorder)). - Store each value's index:
self.inorderIndex[inorder[i]] = i. After the loop, any value's position is one lookup away. - Start the preorder counter
self.preIndexat 0. It points at the next root to use. - Return the whole tree by calling
self.buildwithpreorderand the full range ofinorder, from 0 tolen(inorder) - 1. - Declare the helper
build(self, preorder, lo, hi). It returns the root of the subtree whose values fillinorder[lo..hi]. - Handle the empty range. If
lo > hi, returnNone. Below a leaf, both ranges are empty, so this ends the recursion. - Take the next root from
preorder[self.preIndex]asvalue, then advanceself.preIndex. - Create the node with
TreeNode(value). - Find the split point:
mid = self.inorderIndex[value]. - Build the left subtree from the range
lotomid - 1and store it asnode.left. It goes first because the left subtree's roots follow this node inpreorder. - Build the right subtree from
mid + 1tohiand store it asnode.right. By then the left subtree has consumed all its roots, so the counter points at the right subtree's root. - Return the node.
The solution in Python
class Solution:
def build(self, preorder, lo, hi):
if lo > hi:
return None
value = preorder[self.preIndex]
self.preIndex += 1
node = TreeNode(value)
mid = self.inorderIndex[value]
node.left = self.build(preorder, lo, mid - 1)
node.right = self.build(preorder, mid + 1, hi)
return node
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
self.inorderIndex = {}
for i in range(len(inorder)):
self.inorderIndex[inorder[i]] = i
self.preIndex = 0
return self.build(preorder, 0, len(inorder) - 1)
Python details worth a closer look:
- State on
self.inorderIndexandpreIndexare attributes of theSolutionobject, shared by every call ofbuild. This is what lets the counter advance across the recursion without being passed along. A plain local variable would be a separate copy in each call. - The counter is reset every time.
buildTreesetsself.preIndex = 0on each call, so oneSolutionobject can build several trees in a row. - The order of the two recursive calls matters. The left call must come first, because it uses up the next roots in
preorder. Swapping the two lines would hand the left subtree's roots to the right side. - Dict lookup.
self.inorderIndex[value]raisesKeyErrorfor a missing key, but here the value always exists, since both lists hold the same values.
Complexity
Time: O(n). Filling the dict visits each value once. build creates each node exactly once, with one dict lookup and constant extra work, and returns for each empty range once, so about 2n + 1 calls in all.
Space: O(n). The dict holds n entries and the tree itself has n nodes. The recursion goes as deep as the tree is tall, up to n levels in a one-sided tree; the lesson limits trees to 400 levels, within Python's default recursion limit of 1,000.
Edge cases and common mistakes
- A single value.
preorder = [-1],inorder = [-1]gives a tree with just a root:[-1]. - Lines of nodes. The same
preorder = [1,2,3]gives[1,2,null,3]withinorder = [3,2,1](every node has only a left child) and[1,null,2,null,3]withinorder = [1,2,3](only right children). - Building the right subtree first. The counter then points at a left-subtree value and the tree comes out wrong.
- Using the wrong bounds. The root at
midbelongs to neither range, so the ranges arelo..mid - 1andmid + 1..hi. Usingmidin either one makes a range cover one value too many, so the counter runs ahead of the tree and the result is wrong. - Searching instead of looking up.
inorder.index(value)works but costsO(n)per node, which makes the whole solutionO(n²).
Testing
Each result below is the tree printed level by level:
preorder = [3,9,20,15,7],inorder = [9,3,15,20,7]gives[3,9,20,null,null,15,7].preorder = [-1],inorder = [-1]gives[-1].preorder = [1,2,3],inorder = [3,2,1]gives[1,2,null,3].preorder = [1,2,3],inorder = [1,2,3]gives[1,null,2,null,3].preorder = [1,2,3],inorder = [2,1,3]gives[1,2,3].preorder = [4,2,1,3,6,5,7],inorder = [1,2,3,4,5,6,7]gives[4,2,6,1,3,5,7].
