Problem overview
BuildTree receives two int[] arrays, preorder and inorder, the two traversals of the same binary tree with all values different, and returns the root of that tree. The lesson provides the TreeNode class (val, left, right and a constructor taking the value), as the starter code's comment shows.
For 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 with children 15 and 7. Printed level by level, that is [3,9,20,null,null,15,7].
Approach: preorder names the root, inorder splits the rest
Two facts about the traversals do all the work:
- In preorder, a node comes before its subtrees. So the first value is the root of the whole tree, and in general the next unused value is the root of the next subtree we build.
- In inorder, a node sits between its left subtree and its right subtree. Find the root's position in
inorder: everything to its left is the left subtree, everything to its right is the right subtree.
So take the root from preorder, find it in inorder, and recursively build the left part and then the right part. Building the left subtree first matters: the left subtree's roots come right after this root in preorder, before any root of the right subtree. A single counter that moves forward through preorder therefore always points at the correct next root.
The only slow part would be finding the root's position in inorder. Scanning for it every time costs O(n) per node, O(n^2) overall. Since all values are different, a Dictionary<int, int> from value to index, filled once, answers in constant time.
Step by step
- Field
inorderIndex: aDictionary<int, int>created empty. A field is visible to every method of the class, so the helper can read it. - Field
preIndex: anintstarting at 0, the position inpreorderof the next root. - Fill the map in
BuildTreewith a loop overifrom 0 whilei < inorder.Length, settinginorderIndex[inorder[i]] = i. - Start the recursion: return
Build(preorder, 0, inorder.Length - 1). The whole ofinorderis the whole tree. - Write
Build(int[] preorder, int lo, int hi). The pairloandhimarks the stretch ofinorderholding this subtree's values; every subtree fills one unbroken stretch. - Empty range: if
lo > hi, returnnull. Below a leaf, both ranges are empty, and this is where the recursion stops. - Take the root:
value = preorder[preIndex], thenpreIndex++. - Create the node with
new TreeNode(value). - Locate the root in
inorder:mid = inorderIndex[value]. Indexeslotomid - 1are the left subtree, andmid + 1tohithe right. - Build the left subtree first, with range
lotomid - 1, and store it innode.left. - Build the right subtree with range
mid + 1tohiand store it innode.right. By then the left subtree has used up all its roots, sopreIndexpoints at this subtree's root. - Return
node.
The solution in C#
class Solution {
Dictionary<int, int> inorderIndex = new Dictionary<int, int>();
int preIndex = 0;
public TreeNode Build(int[] preorder, int lo, int hi) {
if (lo > hi) {
return null;
}
int value = preorder[preIndex];
preIndex++;
TreeNode node = new TreeNode(value);
int mid = inorderIndex[value];
node.left = Build(preorder, lo, mid - 1);
node.right = Build(preorder, mid + 1, hi);
return node;
}
public TreeNode BuildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.Length; i++) {
inorderIndex[inorder[i]] = i;
}
return Build(preorder, 0, inorder.Length - 1);
}
}
C# details worth knowing:
- Fields keep the state between calls.
preIndexmust survive from one recursive call to the next, so it is a field and not a local variable. Passing it as a plainintparameter would copy it, and the left call's progress would be lost to the right call. (Aref intparameter would also work.) The consequence is that oneSolutionobject builds one tree: a second call ofBuildTreeon the same object would start withpreIndexwherever the first stopped. - The indexer
inorderIndex[inorder[i]] = iadds or overwrites. Assigning through the indexer is fine for a new key. Reading a key that isn't there throwsKeyNotFoundException, which can't happen here because every value ofpreorderis also ininorder. inorder.Lengthis a property of an array, with no parentheses;Countis the equivalent for aList<T>.- Arrays are passed by reference, so passing
preorderdown the recursion copies nothing.
Complexity
Time: O(n). Filling the dictionary visits each index once. Build creates each node exactly once and does constant work per node: one array read, one dictionary lookup and two calls. The calls that return null number n + 1 in total.
Space: O(n). The dictionary holds n entries. The recursion goes as deep as the tree is tall, up to n levels for a tree that is a straight line (the lesson limits depth to 400).
Edge cases and common mistakes
- A single node.
preorder = [-1],inorder = [-1]gives the tree[-1]. Negative values work like any other, because they are only used as dictionary keys. - All left children.
preorder = [1,2,3]withinorder = [3,2,1]gives[1,2,null,3]. Withinorder = [1,2,3]instead, the same preorder gives a line going right,[1,null,2,null,3]. - Building the right subtree first. The counter would point at the left subtree's root while the code believes it is reading the right subtree's, and the tree would be wrong. The order of the two recursive calls is fixed by preorder.
- Using the wrong range for the right half. The right subtree is
mid + 1tohi, notmidtohi;midis the root itself. - Searching
inorderwith a loop. It works but costsO(n)per node. With a dictionary, each lookup is constant time.
Testing
Running BuildTree and printing the result level by level gives:
| preorder | inorder | Tree |
|---|---|---|
[3,9,20,15,7] | [9,3,15,20,7] | [3,9,20,null,null,15,7] |
[-1] | [-1] | [-1] |
[1,2,3] | [3,2,1] | [1,2,null,3] |
[1,2,3] | [1,2,3] | [1,null,2,null,3] |
For the first input, the calls of Build take their roots in this order, with the stretch of inorder each one covers:
root=3 lo=0 hi=4 mid=1
root=9 lo=0 hi=0 mid=0
root=20 lo=2 hi=4 mid=3
root=15 lo=2 hi=2 mid=2
root=7 lo=4 hi=4 mid=4
The roots come out in exactly the order of preorder, which is why one forward-moving counter is enough.
