Construct Binary Tree from Preorder and Inorder Traversal
MedPrompt
Given two integer arrays preorder and inorder where preorder is the preorder traversal of a binary tree and inorder is the inorder traversal of the same tree, construct and return the binary tree.
Example 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]Example 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
Constraints:
1 <= preorder.length <= 3000inorder.length == preorder.length-3000 <= preorder[i], inorder[i] <= 3000preorderandinorderconsist of unique values.- Each value of
inorderalso appears inpreorder. preorderis guaranteed to be the preorder traversal of the tree.inorderis guaranteed to be the inorder traversal of the tree.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly translates the properties of preorder and inorder traversals into a recursive algorithm. The first element of the preorder array is the root. We find this root in the inorder array to determine the elements of the left and right subtrees. Then, we create new subarrays for the preorder and inorder traversals of the left and right subtrees and make recursive calls.
Algorithm
- If the
preorderarray is empty, returnnull. - The first element of the
preorderarray,preorder[0], is the root of the tree. Create aTreeNodefor this root. - Find the index,
mid, of the root's value in theinorderarray. This is a linear scan. - The elements to the left of
midin theinorderarray form the left subtree. The elements to the right form the right subtree. - The number of nodes in the left subtree is
mid. - Create new subarrays for the left and right subtrees:
left_inorder:inorderfrom index0tomid - 1.right_inorder:inorderfrom indexmid + 1to the end.left_preorder:preorderfrom index1tomid.right_preorder:preorderfrom indexmid + 1to the end.
- Recursively call the function to build the left child:
root.left = buildTree(left_preorder, left_inorder). - Recursively call the function to build the right child:
root.right = buildTree(right_preorder, right_inorder). - Return the
rootnode.
Walkthrough
The core idea is to use recursion. The base case for the recursion is when the input arrays are empty, in which case we return null.
In each recursive step:
- Pick the first element from the
preorderarray. This is the root of the current subtree. - Create a new
TreeNodewith this value. - Search for this root's value in the
inorderarray. Let's say its index isk. - All elements in the
inorderarray to the left ofk(from index 0 tok-1) belong to the left subtree. - All elements in the
inorderarray to the right ofk(from indexk+1to the end) belong to the right subtree. - The number of elements in the left subtree is
k. - The next
kelements in thepreorderarray (after the root) belong to the left subtree's preorder traversal. The rest belong to the right subtree's preorder traversal. - Create new subarrays for the left and right subtrees' traversals (
left_inorder,right_inorder,left_preorder,right_preorder). - Recursively call the function to build the left and right children.
This process is simple to understand but inefficient due to the repeated creation of new arrays and linear scanning in each recursive call.
import java.util.Arrays; /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { if (preorder == null || preorder.length == 0) { return null; } // The first element of preorder is the root TreeNode root = new TreeNode(preorder[0]); // Find the root in the inorder array int mid = -1; for (int i = 0; i < inorder.length; i++) { if (inorder[i] == root.val) { mid = i; break; } } // Create subarrays for left and right subtrees int[] leftInorder = Arrays.copyOfRange(inorder, 0, mid); int[] rightInorder = Arrays.copyOfRange(inorder, mid + 1, inorder.length); int[] leftPreorder = Arrays.copyOfRange(preorder, 1, mid + 1); int[] rightPreorder = Arrays.copyOfRange(preorder, mid + 1, preorder.length); // Recursively build the left and right subtrees root.left = buildTree(leftPreorder, leftInorder); root.right = buildTree(rightPreorder, rightInorder); return root; }}Complexity
Time
O(N^2)
Space
O(N^2)
Trade-offs
Pros
Conceptually simple and easy to understand.
Directly follows the definition of the traversals.
Cons
Highly inefficient in both time and space due to the creation of new array copies in every recursive call.
The repeated linear scan to find the root's index in the inorder array makes it slow for large inputs.
This approach improves upon the brute-force method by avoiding the creation of new arrays. Instead of slicing arrays, we pass pointers or indices to a helper function. These indices define the current boundaries of the preorder and inorder subarrays we are considering, which significantly reduces space overhead.
Algorithm
- Initialize a global
preorderIndex = 0to track the current root in thepreorderarray. - Call a recursive helper function
build(inStart, inEnd)with initial bounds0andinorder.length - 1. - Inside
build(inStart, inEnd):- If
inStart > inEnd, returnnull(base case). - Get the current root's value:
rootVal = preorder[preorderIndex], and then incrementpreorderIndex. - Create a new
TreeNodefor the root. - Linearly scan the
inorderarray frominStarttoinEndto find the indexinRootIndexofrootVal. - The left child is built by the recursive call
build(inStart, inRootIndex - 1). - The right child is built by the recursive call
build(inRootIndex + 1, inEnd). - Return the created
rootnode.
- If
Walkthrough
We define a recursive helper function, say build(inStart, inEnd), which constructs a tree from preorder and a specific segment of inorder defined by inStart and inEnd.
A global or class-level variable, preorderIndex, is used to keep track of the current root in the preorder array. It's initialized to 0 and incremented each time a node is processed.
The algorithm for the helper function build(inStart, inEnd) is as follows:
- Base case: If
inStart > inEnd, it means the current subarray is empty, so returnnull. - Get the current root's value from
preorder[preorderIndex]and incrementpreorderIndex. - Create a new
TreeNodewith this value. - Find the index of this root's value within the current
inordersegment (inorder[inStart...inEnd]). This search is a linear scan. - Recursively call
buildfor the left and right subtrees using the calculated boundaries.
This approach eliminates the space and time overhead of array copying, but the time complexity is still dominated by the linear search in each step.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int val) { this.val = val; } * } */class Solution { int preorderIndex; int[] preorder; int[] inorder; public TreeNode buildTree(int[] preorder, int[] inorder) { this.preorder = preorder; this.inorder = inorder; this.preorderIndex = 0; return build(0, inorder.length - 1); } private TreeNode build(int inStart, int inEnd) { // Base case if (inStart > inEnd) { return null; } // Get current root from preorder and advance index int rootVal = preorder[preorderIndex++]; TreeNode root = new TreeNode(rootVal); // Find root's index in the current inorder segment int inRootIndex = -1; for (int i = inStart; i <= inEnd; i++) { if (inorder[i] == rootVal) { inRootIndex = i; break; } } // Recursively build left and right subtrees // Important: Build left subtree first because preorderIndex is advanced sequentially root.left = build(inStart, inRootIndex - 1); root.right = build(inRootIndex + 1, inEnd); return root; }}Complexity
Time
O(N^2)
Space
O(N)
Trade-offs
Pros
More space-efficient than the brute-force approach as it avoids creating new arrays.
Reduces memory allocation overhead.
Cons
The time complexity is still quadratic in the worst case due to the repeated linear search for the root's index in the
inorderarray.
This is the most efficient approach. It builds upon the previous recursive approach but optimizes the process of finding the root's index in the inorder array. By pre-processing the inorder array into a HashMap that maps each value to its index, we can find the root's index in O(1) time, which reduces the overall time complexity to linear.
Algorithm
- Create a
HashMap<Integer, Integer>calledinorderMapto store(value, index)pairs from theinorderarray. This takesO(N)time. - Initialize a global
preorderIndex = 0. - Call a recursive helper
build(inStart, inEnd)with initial bounds0andinorder.length - 1. - Inside
build(inStart, inEnd):- If
inStart > inEnd, returnnull. - Get
rootVal = preorder[preorderIndex]and incrementpreorderIndex. - Create a
rootnode withrootVal. - Get the root's index
inRootIndexfrom theinorderMapinO(1)time:inRootIndex = inorderMap.get(rootVal). - Set
root.left = build(inStart, inRootIndex - 1). - Set
root.right = build(inRootIndex + 1, inEnd). - Return
root.
- If
Walkthrough
The overall recursive structure is the same as the previous approach, using indices to define subarrays. The key improvement is the elimination of the linear search.
The algorithm is as follows:
- First, create a
HashMapto store the indices of elements in theinorderarray. This takesO(N)time. - Initialize a global or class-level variable,
preorderIndex, to 0. - Call a recursive helper function,
build(inStart, inEnd), with the initial boundaries of theinorderarray. - Inside the helper function, get the current root from
preorder, create the node, and then use theHashMapto find its position in theinordersegment inO(1)time. - Recursively build the left and right subtrees.
By replacing the O(N) search with an O(1) lookup, the work done at each node becomes constant, leading to an overall linear time complexity.
import java.util.HashMap;import java.util.Map; /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int val) { this.val = val; } * } */class Solution { int preorderIndex; Map<Integer, Integer> inorderMap; int[] preorder; public TreeNode buildTree(int[] preorder, int[] inorder) { this.preorder = preorder; this.preorderIndex = 0; // Build a hashmap to store value -> index relations this.inorderMap = new HashMap<>(); for (int i = 0; i < inorder.length; i++) { inorderMap.put(inorder[i], i); } return build(0, inorder.length - 1); } private TreeNode build(int inStart, int inEnd) { // Base case if (inStart > inEnd) { return null; } // Get current root from preorder and advance index int rootVal = preorder[preorderIndex++]; TreeNode root = new TreeNode(rootVal); // Find root's index in inorder using the map (O(1) lookup) int inRootIndex = inorderMap.get(rootVal); // Recursively build left and right subtrees // Important: Build left subtree first because preorderIndex is advanced sequentially root.left = build(inStart, inRootIndex - 1); root.right = build(inRootIndex + 1, inEnd); return root; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Optimal time complexity of O(N).
Efficiently solves the problem by removing the repetitive search bottleneck.
Cons
Requires extra space for the HashMap, which might be a concern for very strict memory constraints.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { private int [] preorder ; private Map < Integer , Integer > d = new HashMap <>(); public TreeNode buildTree ( int [] preorder , int [] inorder ) { int n = preorder . length ; this . preorder = preorder ; for ( int i = 0 ; i < n ; ++ i ) { d . put ( inorder [ i ], i ); } return dfs ( 0 , 0 , n ); } private TreeNode dfs ( int i , int j , int n ) { if ( n <= 0 ) { return null ; } int v = preorder [ i ]; int k = d . get ( v ); TreeNode l = dfs ( i + 1 , j , k - j ); TreeNode r = dfs ( i + 1 + k - j , k + 1 , n - 1 - ( k - j )); return new TreeNode ( v , l , r ); } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {number[]} preorder * @param {number[]} inorder * @return {TreeNode} */ var buildTree = function (preorder, inorder) { const d = new Map(); const n = inorder.length; for (let i = 0; i < n; ++i) { d.set(inorder[i], i); } const dfs = (i, j, n) => { if (n <= 0) { return null; } const v = preorder[i]; const k = d.get(v); const l = dfs(i + 1, j, k - j); const r = dfs(i + 1 + k - j, k + 1, n - 1 - (k - j)); return new TreeNode(v, l, r); }; return dfs(0, 0, n); };/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: TreeNode * buildTree ( vector < int >& preorder , vector < int >& inorder ) { int n = preorder . size (); unordered_map < int , int > d ; for ( int i = 0 ; i < n ; ++ i ) { d [ inorder [ i ]] = i ; } function < TreeNode * ( int , int , int ) > dfs = [ & ]( int i , int j , int n ) -> TreeNode * { if ( n <= 0 ) { return nullptr ; } int v = preorder [ i ]; int k = d [ v ]; TreeNode * l = dfs ( i + 1 , j , k - j ); TreeNode * r = dfs ( i + 1 + k - j , k + 1 , n - 1 - ( k - j )); return new TreeNode ( v , l , r ); }; return dfs ( 0 , 0 , n ); } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right # best class Solution : def buildTree ( self , preorder : List [ int ], inorder : List [ int ]) -> Optional [ TreeNode ]: if not preorder : # or, not inorder return None v = preorder [ 0 ] i = inorder . index ( v ) root = TreeNode ( v ) root . left = self . buildTree ( preorder [ 1 : i + 1 ], inorder [: i ]) root . right = self . buildTree ( preorder [ i + 1 :], inorder [ i + 1 :]) return root # inorder.index(preorder_val) class Solution : def buildTree ( self , preorder : List [ int ], inorder : List [ int ]) -> Optional [ TreeNode ]: def dfs ( pleft , pright , ileft , iright ): if pleft > pright or ileft > iright : return None k = inorder . index ( preorder [ pleft ]) root = TreeNode ( preorder [ pleft ]) root . left = dfs ( pleft + 1 , pleft + ( k - ileft ), ileft , k - 1 ) root . right = dfs ( pleft + 1 + ( k - ileft ), pright , k + 1 , iright ) return root n = len ( preorder ) return dfs ( 0 , n - 1 , 0 , n - 1 ) # build dict for val => index class Solution : def buildTree ( self , preorder : List [ int ], inorder : List [ int ]) -> Optional [ TreeNode ]: def dfs ( pleft , pright , ileft , iright ): if pleft > pright or ileft > iright : return None v = preorder [ pleft ] k = d [ v ] root = TreeNode ( v ) root . left = dfs ( pleft + 1 , pleft + ( k - ileft ), ileft , k - 1 ) root . right = dfs ( pleft + 1 + ( k - ileft ), pright , k + 1 , iright ) return root d = { v : i for i , v in enumerate ( inorder )} n = len ( preorder ) return dfs ( 0 , n - 1 , 0 , n - 1 ) ############ ''' search for index, also use .index(val) >>> a = [1,2,3,4,5] >>> a.index(3) 2 ''' class Solution ( object ): def buildTree ( self , preorder , inorder ): """ :type preorder: List[int] :type inorder: List[int] :rtype: TreeNode """ self . preindex = 0 ind = { v : i for i , v in enumerate ( inorder )} head = self . dc ( 0 , len ( preorder ) - 1 , preorder , inorder , ind ) return head def dc ( self , start , end , preorder , inorder , ind ): if start <= end : mid = ind [ preorder [ self . preindex ]] self . preindex += 1 root = TreeNode ( inorder [ mid ]) root . left = self . dc ( start , mid - 1 , preorder , inorder , ind ) root . right = self . dc ( mid + 1 , end , preorder , inorder , ind ) return root ############ # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def buildTree ( self , preorder : List [ int ], inorder : List [ int ]) -> Optional [ TreeNode ]: def dfs ( i : int , j : int , n : int ): if n <= 0 : return None v = preorder [ i ] k = d [ v ] l = dfs ( i + 1 , j , k - j ) r = dfs ( i + 1 + k - j , k + 1 , n - k + j - 1 ) return TreeNode ( v , l , r ) d = { v : i for i , v in enumerate ( inorder )} return dfs ( 0 , 0 , len ( preorder ))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.