You are given two integer arrays, preorder
and inorder
, representing the preorder and inorder traversals of a binary tree, respectively. Your task is to reconstruct and return the binary tree. Assume that the tree contains unique values.
For example, consider the following traversals:
preorder = [3, 9, 20, 15, 7]
inorder = [9, 3, 15, 20, 7]
The expected output would be the binary tree represented by the root node 3, where the left child is 9, and the right child is 20. The node 20 has a left child of 15 and a right child of 7.
As another example:
preorder = [-1]
inorder = [-1]
In this case, the output should be a binary tree containing a single node with the value -1.
Describe an algorithm to solve this problem efficiently, considering both time and space complexity. What are the edge cases to consider? Can you provide the code to implement this?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you're assembling a tree, but you only have instructions that say how to visit the tree in two different orders. The brute force approach tries every possible tree structure to see if it matches those instructions.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def construct_binary_tree_brute_force(preorder, inorder):
if not preorder or not inorder:
return None
# The first element of the preorder traversal is the root.
root_value = preorder[0]
root_node = TreeNode(root_value)
root_index_inorder = inorder.index(root_value)
# Split inorder into left and right subtrees.
left_inorder = inorder[:root_index_inorder]
right_inorder = inorder[root_index_inorder + 1:]
# Deduce preorder traversals for the left and right subtrees.
left_preorder = preorder[1:len(left_inorder) + 1]
right_preorder = preorder[len(left_inorder) + 1:]
# Recursively construct the left and right subtrees.
root_node.left = construct_binary_tree_brute_force(left_preorder, left_inorder)
root_node.right = construct_binary_tree_brute_force(right_preorder, right_inorder)
return root_node
def inorder_traversal(node):
if node:
yield from inorder_traversal(node.left)
yield node.value
yield from inorder_traversal(node.right)
def preorder_traversal(node):
if node:
yield node.value
yield from preorder_traversal(node.left)
yield from preorder_traversal(node.right)
def verify_tree(root, preorder, inorder):
generated_preorder = list(preorder_traversal(root))
generated_inorder = list(inorder_traversal(root))
return generated_preorder == preorder and generated_inorder == inorder
def construct_binary_tree_brute_force_wrapper(preorder, inorder):
import itertools
# If the input is empty, there is nothing to construct
if not preorder or not inorder:
return None
# Need a way to try every single possible binary tree.
# Here, we directly create and test only one tree to match the prompt
# specification without true bruteforcing.
root_node = construct_binary_tree_brute_force(preorder, inorder)
# Check if this tree is valid.
if verify_tree(root_node, preorder, inorder):
return root_node
else:
return None
The core idea is to use the preorder traversal to guide the tree construction, while the inorder traversal helps us determine the left and right subtrees. Think of preorder as telling you the root, and inorder telling you what's on either side of that root.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value=0, left=None, right=None):
self.value = value
self.left = left
self.right = right
def build_tree_from_preorder_inorder(preorder, inorder):
preorder_index = 0
inorder_index_map = {value: index for index, value in enumerate(inorder)}
def build_tree(inorder_start, inorder_end):
nonlocal preorder_index
# If the inorder list is empty, there is no subtree to build.
if inorder_start > inorder_end:
return None
# The next element in preorder is the root of the current subtree.
root_value = preorder[preorder_index]
root = TreeNode(root_value)
preorder_index += 1
# Find the index of the root value in inorder traversal.
inorder_root_index = inorder_index_map[root_value]
# Construct the left subtree recursively.
# Elements before the root in inorder are in the left subtree.
root.left = build_tree(inorder_start, inorder_root_index - 1)
# Construct the right subtree recursively.
# Elements after the root in inorder are in the right subtree.
root.right = build_tree(inorder_root_index + 1, inorder_end)
return root
return build_tree(0, len(inorder) - 1)
Case | How to Handle |
---|---|
Null or empty preorder and inorder arrays | Return null or an empty tree indicating no tree can be constructed. |
Preorder and inorder arrays have different lengths | Throw an IllegalArgumentException as the traversals must represent the same tree. |
Single node tree (arrays of size 1) | Correctly creates a tree with a single node and returns it. |
Complete binary tree (balanced structure) | Solution should efficiently construct a balanced tree. |
Skewed tree (highly unbalanced, all nodes on one side) | Solution might exhibit poor performance due to high recursion depth but should still construct the tree correctly. |
Duplicate values in the inorder array | The index mapping in the inorder array may become ambiguous, leading to an incorrect tree construction or infinite recursion; handle by clarifying problem constraints to prohibit duplicates or choose the first occurrence. |
Integer overflow in node values (very large positive or negative numbers) | The solution should handle large integer values without causing overflow issues during processing or comparisons assuming the language supports sufficiently large integers or BigInteger is used. |
Invalid preorder/inorder combination (no valid tree possible) | Throw an exception or return null, signifying that the given traversals cannot form a valid binary tree if no valid tree can be formed. |