Given two integer arrays, inorder
and postorder
, representing the inorder and postorder traversals of a binary tree, respectively, construct and return the binary tree. Assume that both arrays contain unique elements and that postorder
corresponds to the postorder traversal and inorder
is the inorder traversal of the same tree.
For example:
If inorder = [9,3,15,20,7]
and postorder = [9,15,7,20,3]
, the expected output is a binary tree with the structure:
3
/ \
9 20
/ \
15 7
If inorder = [-1]
and postorder = [-1]
, the expected output is a binary tree with a single node: -1
.
Clarify your assumptions about the input, especially regarding empty inputs, the presence of duplicate values, and the guaranteed validity of the traversals.
How would you approach constructing the binary tree from these traversals? Can you describe a step-by-step algorithm? What are the time and space complexity considerations for your approach? Consider edge cases such as empty arrays or arrays representing a single node tree.
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:
We need to build a tree using two lists that tell us how to visit the nodes: one goes in order (left, root, right) and the other goes root last (left, right, root). The brute force method tries every single possible tree structure that matches these visit orders, until it finds the correct one.
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 construct_binary_tree_brute_force(inorder, postorder):
def build_tree(inorder_list, postorder_list):
# If either list is empty, there's no tree to build.
if not inorder_list or not postorder_list:
return None
# Last element in postorder is always the root.
root_value = postorder_list[-1]
root_node = TreeNode(root_value)
root_index_inorder = inorder_list.index(root_value)
# Divide inorder list to left and right subtrees.
left_inorder = inorder_list[:root_index_inorder]
right_inorder = inorder_list[root_index_inorder + 1:]
# Calculate the sizes of left and right subtrees for postorder.
left_size = len(left_inorder)
# Construct the left and right postorder sublists.
left_postorder = postorder_list[:left_size]
right_postorder = postorder_list[left_size:-1]
root_node.left = build_tree(left_inorder, left_postorder)
root_node.right = build_tree(right_inorder, right_postorder)
return root_node
def inorder_traversal(root):
if root:
return inorder_traversal(root.left) + [root.value] + inorder_traversal(root.right)
return []
def postorder_traversal(root):
if root:
return postorder_traversal(root.left) + postorder_traversal(root.right) + [root.value]
return []
def verify_tree(possible_root):
# Generate traversals from the constructed tree
inorder_result = inorder_traversal(possible_root)
postorder_result = postorder_traversal(possible_root)
# Compare with the original traversals
return inorder_result == inorder and postorder_result == postorder
import itertools
# Generate all possible permutations for root selection
possible_trees = []
def generate_trees(inorder_list, postorder_list):
if not inorder_list:
return [None]
trees = []
for i in range(len(postorder_list)):
root_value = postorder_list[i]
root_index_inorder = inorder_list.index(root_value)
left_inorder = inorder_list[:root_index_inorder]
right_inorder = inorder_list[root_index_inorder+1:]
left_postorder = postorder_list[:i]
right_postorder = postorder_list[i:-1]
# Recursively build the left and right subtrees
left_trees = generate_trees(left_inorder, left_postorder)
right_trees = generate_trees(right_inorder, right_postorder)
# Combine each possible left and right subtree with the current root
for left_tree in left_trees:
for right_tree in right_trees:
root_node = TreeNode(root_value)
root_node.left = left_tree
root_node.right = right_tree
trees.append(root_node)
return trees
all_possible_trees = generate_trees(inorder, postorder)
# Check each possible tree until we find the correct one
for possible_root in all_possible_trees:
if verify_tree(possible_root):
return possible_root
return None
We can build the tree without guesswork by leveraging the properties of inorder and postorder traversals. The last element in the postorder traversal is always the root of the (sub)tree. We can use this root to split the inorder traversal into left and right subtrees, and then recursively apply the same logic to build the entire tree.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def buildTree(inorder, postorder):
if not inorder or not postorder:
return None
# The last element of postorder is the root
root_value = postorder[-1]
root = TreeNode(root_value)
# Find the index of the root in inorder list
root_index_inorder = inorder.index(root_value)
# Calculate sizes of left and right subtrees
left_subtree_size = root_index_inorder
# Recursively build the left subtree
root.left = buildTree(
inorder[:left_subtree_size],
postorder[:left_subtree_size]
)
# Recursively build the right subtree.
# postorder[left_subtree_size:-1] selects the postorder traversal for the right subtree.
root.right = buildTree(
inorder[root_index_inorder + 1:],
postorder[left_subtree_size:-1]
)
return root
Case | How to Handle |
---|---|
Empty inorder and postorder arrays | Return null (or equivalent representation of an empty tree) since there's no tree to construct. |
Inorder and postorder arrays of different lengths | Throw an exception or return null, indicating an invalid input, as tree construction is impossible. |
Single node tree (arrays with one element) | Create a single node tree with the value from the single element and return it. |
Skewed tree (e.g., only left or only right children) | The recursive algorithm should handle skewed trees correctly as the index manipulation ensures proper left/right subtree construction. |
Duplicate values in the inorder array | The algorithm should still correctly build the tree if duplicate values exist in the inorder array, albeit with potentially non-unique tree structure. |
Large tree depth causing stack overflow (if using recursion) | Consider iterative approach with explicit stack to avoid potential stack overflow errors for very deep trees. |
Input arrays contain negative or zero values | The algorithm should function correctly with negative or zero values as node values are generally treated as data regardless of their sign. |
Postorder does not represent the traversal of the inorder tree | The algorithm will likely return a malformed tree or get stuck in an infinite loop; input validation for consistency should be included. |