150. Construct Binary Tree from Inorder and Postorder Traversal
π LeetCode Problem: 106. Construct Binary Tree from Inorder and Postorder Traversal
π Difficulty: Medium
π·οΈ Topics: Binary Trees, Array, Divide and Conquer, Recursion, HashMap
Problem Statement
Given two integer arrays inorder and postorder where:
- inorder is the inorder traversal of a binary tree
- postorder is the postorder traversal of the same tree
Construct and return the binary tree.
Example 1:
Input: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
Output: [3,9,20,null,null,15,7]
Example 2:
Input: inorder = [-1], postorder = [-1]
Output: [-1]
Constraints:
- 1 <= inorder.length <= 3000
- postorder.length == inorder.length
- -3000 <= inorder[i], postorder[i] <= 3000
- inorder and postorder consist of unique values
- Each value of postorder also appears in inorder
- inorder is guaranteed to be the inorder traversal of the tree
- postorder is guaranteed to be the postorder traversal of the tree
π³ Visual Understanding - Comparing with Preorder
Traversal Comparison:
PREORDER (Root β Left β Right):
[Root, ...Left..., ...Right...]
β
ROOT at START
POSTORDER (Left β Right β Root):
[...Left..., ...Right..., Root]
β
ROOT at END
INORDER (Left β Root β Right):
[...Left..., Root, ...Right...]
β
ROOT in MIDDLE (splits left/right)
Key difference: Root position! π
Example - Visual Breakdown:
Given:
inorder = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]
STEP 1: Find root from postorder
postorder[last] = 3
Root = 3 β
STEP 2: Find root in inorder
inorder = [9, | 3 |, 15, 20, 7]
β β β
left root right
STEP 3: Partition inorder
Left subtree: [9]
Right subtree: [15, 20, 7]
STEP 4: Partition postorder accordingly
Left: [9] (first 1 element)
Right: [15, 7, 20] (next 3 elements)
Root: [3] (last element)
STEP 5: Current tree structure
3
/ \
? ?
/ \
[9] [15,20,7]
Recursively build left and right!
Visual Comparison with Preorder Problem:
PROBLEM 149 (Preorder + Inorder):
preorder = [3, 9, 20, 15, 7]
β β β---------β
root left right
Root at START
PROBLEM 150 (Inorder + Postorder):
postorder = [9, 15, 7, 20, 3]
β β---------β β
left right root
Root at END
BOTH: Inorder splits left/right
DIFFERENCE: Where to find root! π―
π§ The AHA Moment - Same Pattern, Different Root!
The Core Algorithm:
buildTree(inorder, postorder):
1. Root = postorder[LAST] β KEY DIFFERENCE!
2. Find root index in inorder
3. Elements before root in inorder = left subtree
4. Elements after root in inorder = right subtree
5. Partition postorder based on sizes
6. Recursively build left and right subtrees
7. Connect and return
Almost IDENTICAL to Problem 149! β¨
Visual Algorithm Flow:
inorder = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]
ββββββββββββββββββββββββββββββββββββββ
β Find root: postorder[LAST] = 3 β
β Find in inorder: index 1 β
β β
β Partition inorder: β
β Left: [9] (indices 0-0) β
β Right: [15,20,7] (indices 2-4) β
β β
β Partition postorder: β
β Left size = 1 β
β Left: [9] (indices 0-0) β
β Right: [15,7,20] (indices 1-3) β
β Root: [3] (index 4) β
ββββββββββββββββββββββββββββββββββββββ
β
Recurse on left and right
π§ Recursive Thinking - Building the Solution
Base Case:
When to stop?
β When postorder or inorder range is empty
What to return?
β null (no tree)
if (postStart > postEnd) return null;
// OR
if (inStart > inEnd) return null;
Recursive Case:
For any valid range:
STEP 1: Create root from postorder LAST
int rootVal = postorder[postEnd]; β KEY!
TreeNode root = new TreeNode(rootVal);
STEP 2: Find root in inorder
int rootIndex = inorderMap.get(rootVal);
STEP 3: Calculate left subtree size
int leftSize = rootIndex - inStart;
STEP 4: Recursively build left subtree
root.left = buildTree(
inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSize - 1
);
STEP 5: Recursively build right subtree
root.right = buildTree(
inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSize, postEnd - 1
);
STEP 6: Return constructed root
return root;
Key Differences from Preorder:
PREORDER (Problem 149):
Root = preorder[preStart] (FIRST)
Left: [preStart+1 to preStart+leftSize]
Right: [preStart+leftSize+1 to preEnd]
POSTORDER (Problem 150):
Root = postorder[postEnd] (LAST)
Left: [postStart to postStart+leftSize-1]
Right: [postStart+leftSize to postEnd-1]
Same logic, different indices! π―
π― Solution: Divide and Conquer with HashMap
Implementation:
class Solution {
private Map<Integer, Integer> inorderMap;
public TreeNode buildTree(int[] inorder, int[] postorder) {
// Build hashmap for O(1) lookup of root in inorder
// WHY? Same reason as Problem 149
inorderMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
inorderMap.put(inorder[i], i);
}
// Start recursive building
return build(inorder, 0, inorder.length - 1,
postorder, 0, postorder.length - 1);
}
private TreeNode build(int[] inorder, int inStart, int inEnd,
int[] postorder, int postStart, int postEnd) {
// Base case: no elements to construct tree
if (inStart > inEnd || postStart > postEnd) {
return null;
}
// STEP 1: Last element in postorder is root
// WHY? Postorder visits root LAST! (LeftβRightβRoot)
int rootVal = postorder[postEnd];
TreeNode root = new TreeNode(rootVal);
// STEP 2: Find root position in inorder
// WHY? Same as preorder - inorder splits left/right
int rootIndex = inorderMap.get(rootVal);
// STEP 3: Calculate left subtree size
// WHY? Need to know how to partition postorder
int leftSize = rootIndex - inStart;
// STEP 4: Recursively build left subtree
// Inorder: elements before root
// Postorder: first 'leftSize' elements (left comes before right in postorder)
root.left = build(inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSize - 1);
// STEP 5: Recursively build right subtree
// Inorder: elements after root
// Postorder: middle section (after left, before root)
root.right = build(inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSize, postEnd - 1);
// STEP 6: Return constructed subtree
return root;
}
}
Postorder Index Logic:
postorder = [Left elements, Right elements, Root]
β-----------β β-------------β β
leftSize rightSize 1
If leftSize = 2:
postorder indices:
Left: [postStart to postStart+1] (2 elements)
Right: [postStart+2 to postEnd-1] (remaining before root)
Root: [postEnd] (last element)
Key: Right goes up to postEnd-1 (exclude root!) π
π Detailed Dry Run - Complete Recursion
inorder = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]
inorderMap = {9:0, 3:1, 15:2, 20:3, 7:4}
Complete Call Stack Visualization:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
CALL STACK - BUILDING THE TREE
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Level 0: build(in[0-4], post[0-4])
β
ββ rootVal = postorder[4] = 3 β LAST element!
ββ Create node: TreeNode(3)
ββ rootIndex = inorderMap.get(3) = 1
ββ leftSize = 1 - 0 = 1
β
ββ LEFT: build(in[0-0], post[0-0]) βββββββββββββββ
β β
β Level 1: build(in[0-0], post[0-0]) β
β β β
β ββ inStart=0, inEnd=0 β
β ββ postStart=0, postEnd=0 β
β β β
β ββ rootVal = postorder[0] = 9 β
β ββ Create node: TreeNode(9) β
β ββ rootIndex = inorderMap.get(9) = 0 β
β ββ leftSize = 0 - 0 = 0 β
β β β
β ββ LEFT: build(in[0--1], post[0--1]) β
β β β inStart > inEnd β return null β
β β β
β ββ RIGHT: build(in[1-0], post[0--1]) β
β β β inStart > inEnd β return null β
β β β
β ββ return TreeNode(9) β
β ββ Node structure: 9 β
β / \ β
β null null β
β β
ββ 3.left = TreeNode(9) β
β β
ββ RIGHT: build(in[2-4], post[1-3]) βββββββββββββββ
β β
β Level 1: build(in[2-4], post[1-3]) β
β β β
β ββ inStart=2, inEnd=4 β
β ββ postStart=1, postEnd=3 β
β β β
β ββ rootVal = postorder[3] = 20 β LAST! β
β ββ Create node: TreeNode(20) β
β ββ rootIndex = inorderMap.get(20) = 3 β
β ββ leftSize = 3 - 2 = 1 β
β β β
β ββ LEFT: build(in[2-2], post[1-1]) ββββββ β
β β β β
β β Level 2: build(in[2-2], post[1-1]) β β
β β β β β
β β ββ rootVal = postorder[1] = 15 β β
β β ββ Create node: TreeNode(15) β β
β β ββ rootIndex = 2 β β
β β ββ leftSize = 0 β β
β β β β β
β β ββ LEFT: null β β
β β ββ RIGHT: null β β
β β β β β
β β ββ return TreeNode(15) β β
β β β β
β ββ 20.left = TreeNode(15) β β
β β β
β ββ RIGHT: build(in[4-4], post[2-2]) ββββββ β
β β β β
β β Level 2: build(in[4-4], post[2-2]) β β
β β β β β
β β ββ rootVal = postorder[2] = 7 β β
β β ββ Create node: TreeNode(7) β β
β β ββ rootIndex = 4 β β
β β ββ leftSize = 0 β β
β β β β β
β β ββ LEFT: null β β
β β ββ RIGHT: null β β
β β β β β
β β ββ return TreeNode(7) β β
β β β β
β ββ 20.right = TreeNode(7) β β
β β β
β ββ return TreeNode(20) β
β ββ Node structure: 20 β
β / \ β
β 15 7 β
β β
ββ 3.right = TreeNode(20) β
β β
ββ return TreeNode(3) β
ββ Final tree: 3 β
/ \ β
9 20 β
/ \ β
15 7 β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
FINAL RESULT: Complete binary tree constructed! β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Postorder Processing Order:
postorder = [9, 15, 7, 20, 3]
β β β β β
1 2 3 4 5 (order processed)
Process backwards:
1st: Root 3 (index 4)
2nd: Root of right (20) (index 3)
3rd: Root of right-right (7) (index 2)
4th: Root of right-left (15) (index 1)
5th: Root of left (9) (index 0)
We process RIGHT before LEFT in recursive calls!
But within postorder array, LEFT comes before RIGHT!
π Visual Index Calculations
Understanding Postorder Partitioning:
Given:
inorder = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]
β
idx 4 (root)
At root (3):
rootIndex in inorder = 1
inStart = 0
leftSize = rootIndex - inStart = 1 - 0 = 1
Postorder partitioning:
Left: postorder[0 to 0+leftSize-1]
= postorder[0 to 0]
= [9]
Right: postorder[0+leftSize to end-1]
= postorder[1 to 3]
= [15, 7, 20]
Root: postorder[end]
= postorder[4]
= [3]
Inorder partitioning:
Left: inorder[0 to rootIndex-1]
= inorder[0 to 0]
= [9]
Right: inorder[rootIndex+1 to end]
= inorder[2 to 4]
= [15, 20, 7]
Comparison with Preorder:
PREORDER + INORDER:
preorder = [3, 9, 20, 15, 7]
β β β---------β
root L Right
Left: [preStart+1 to preStart+leftSize]
Right: [preStart+leftSize+1 to preEnd]
POSTORDER + INORDER:
postorder = [9, 15, 7, 20, 3]
β β---------β β
L Right root
Left: [postStart to postStart+leftSize-1]
Right: [postStart+leftSize to postEnd-1]
Mirror image! πͺ
π Complexity Analysis
Time Complexity: O(n)
HashMap construction: O(n)
Recursive construction: O(n)
Visit each node once
O(1) lookup per node
Total: O(n) β
Space Complexity: O(n)
HashMap: O(n)
Recursion stack: O(h)
Best: O(log n)
Worst: O(n)
Total: O(n)
β οΈ Common Mistakes
Mistake 1: Using preStart Instead of postEnd for Root
// β WRONG - Using first element like preorder
int rootVal = postorder[postStart]; // WRONG!
// Postorder visits root LAST, not first!
// β CORRECT - Use last element
int rootVal = postorder[postEnd]; β
Mistake 2: Wrong Right Subtree Range
// β WRONG - Not excluding root
root.right = build(inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSize, postEnd); // Includes root!
// Problem: postEnd is the ROOT!
// Right subtree should end at postEnd-1
// β CORRECT - Exclude root (postEnd-1)
root.right = build(inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSize, postEnd - 1); β
Mistake 3: Wrong Left Subtree Range
// β WRONG - Off by one
root.left = build(inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSize); // One too many!
// Problem: Should be leftSize-1 elements
// [postStart to postStart+leftSize] = leftSize+1 elements!
// β CORRECT - Use leftSize-1
root.left = build(inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSize - 1); β
Mistake 4: Processing Order Confusion
// β CONCEPTUAL ERROR - Thinking we process left first
// "Postorder is left-right-root, so I process left first in recursion"
// WRONG THINKING! The array is left-right-root,
// but we BUILD from root (which is at END)!
// We find root at END, then recursively build:
// 1. Left subtree (from left part of postorder)
// 2. Right subtree (from right part of postorder)
// The ARRAY order β BUILDING order!
Mistake 5: Not Understanding Index Mapping
// β WRONG - Copying preorder logic exactly
root.left = build(inorder, inStart, rootIndex - 1,
postorder, postStart + 1, postStart + leftSize); // WRONG!
// Problem: This is preorder logic (skip first for root)
// In postorder, root is at END, not START!
// β CORRECT - Start from postStart (no skip needed)
root.left = build(inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSize - 1); β
π― Pattern Recognition - Reconstruction Variations
All Reconstruction Patterns:
1. Preorder + Inorder (Problem 149):
Root = preorder[START]
Left: [START+1 to START+leftSize]
Right: [START+leftSize+1 to END]
2. Postorder + Inorder (Problem 150):
Root = postorder[END]
Left: [START to START+leftSize-1]
Right: [START+leftSize to END-1]
3. Preorder only (BST):
Use value comparison
Smaller than root β left
Larger than root β right
Core pattern: Find root, partition, recurse! π―
Why Some Combinations Don't Work:
β Preorder + Postorder (NO INORDER):
Can't uniquely determine tree!
Example:
pre = [1, 2]
post = [2, 1]
Could be: 1 OR 1
/ \
2 2
Need inorder to split left/right!
β Any traversal + Inorder = Unique tree
β Preorder only works for BST (has order property)
π Quick Revision Notes
π― Core Concept
Build tree from inorder and postorder arrays. Postorder gives root (LAST element), inorder partitions left/right. Almost identical to Problem 149, but root at END instead of START. Use HashMap for O(1) lookup. Calculate leftSize to partition correctly. Key difference: postorder ranges must exclude root at end.
β‘ Quick Implementation
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
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;
}
public static TreeNode buildTree(Integer[] arr) {
if (arr == null || arr.length == 0 || arr[0] == null) {
return null;
}
TreeNode root = new TreeNode(arr[0]);
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while (!queue.isEmpty() && i < arr.length) {
TreeNode current = queue.poll();
// Process left child
if (i < arr.length) {
if (arr[i] != null) {
current.left = new TreeNode(arr[i]);
queue.offer(current.left);
}
i++;
}
// Process right child
if (i < arr.length) {
if (arr[i] != null) {
current.right = new TreeNode(arr[i]);
queue.offer(current.right);
}
i++;
}
}
return root;
}
public static void print(TreeNode root) {
if (root == null) {
System.out.println("[]");
return;
}
List<String> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node != null) {
result.add(String.valueOf(node.val));
queue.offer(node.left);
queue.offer(node.right);
} else {
result.add("null");
}
}
while (!result.isEmpty() && result.get(result.size() - 1).equals("null")) {
result.remove(result.size() - 1);
}
System.out.println(result);
}
}
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
int postorderStart = 0;
int postorderEnd = postorder.length - 1;
int inorderStart = 0;
int inorderEnd = inorder.length - 1;
HashMap<Integer, Integer> inorderMap = new HashMap<>();
for(int i = 0; i < inorder.length; i++) {
inorderMap.put(inorder[i], i);
}
return buildTree(postorderStart, postorderEnd, inorderStart, inorderEnd, inorderMap, postorder, inorder);
}
private TreeNode buildTree(int postorderStart, int postorderEnd, int inorderStart, int inorderEnd,
HashMap<Integer,Integer> inorderMap, int[] postorder, int[] inorder) {
// base case - boundary checks
if(postorderStart > postorderEnd || inorderStart > inorderEnd) { // CHANGE
return null; // this case should never exist and may exist for leaf nodes. Returns null.
}
// step 1: always end element of the postorder array is the root element.
// why postorderEnd instead of 0? It will not be always 0 as we divide array more number of subarrays later in the future.
int rootElement = postorder[postorderEnd];
// Get index of root element in inorder array.
// It is guaranteed that all elements are unique.
// Element before this rootElementIndex belong to LST and after this rootElementIndex belong to RST.
int rootElementIndex = inorderMap.get(rootElement);
// Step 2: Initialize root with root element and constrct LST and RST.
TreeNode root = new TreeNode(rootElement);
// Step 3: This is the ONLY GOTCHA of this whole problem. How much (size of LST) I have to take in preorder array is the main issue?
// This is not the issue with inorder array as root element index already decides that.
int leftSubtreeSize = rootElementIndex - inorderStart;
// Step 4: Construct LST with the remains of preorder and inorder arrays.
// All are intuitive if you put on paper except a little care on leftSubtreeSize.
// CHANGE: postorderStart, postorderStart + leftSubtreeSize - 1 since element is taken at the end now.
root.left = buildTree(postorderStart, postorderStart + leftSubtreeSize - 1, inorderStart, rootElementIndex - 1, inorderMap, postorder, inorder);
// Step 5: Construct RST with the remains of preorder and inorder arrays.
// All are intuitive if you put on paper
// CHANGE: postorderStart + leftSubtreeSize, postorderEnd - 1 since element is taken at the end now.
root.right = buildTree(postorderStart + leftSubtreeSize, postorderEnd - 1, rootElementIndex + 1, inorderEnd, inorderMap, postorder, inorder);
return root;
}
public static void main(String[] args) {
Solution s = new Solution();
TreeNode res1 = s.buildTree(new int[] {9,3,15,20,7}, new int[] {9,15,7,20,3});
TreeNode.print(res1);
}
}
π Key Insights
Postorder vs Preorder:
PREORDER (Problem 149):
[Root, ...Left..., ...Right...]
β
Root at START β use preorder[preStart]
POSTORDER (Problem 150):
[...Left..., ...Right..., Root]
β
Root at END β use postorder[postEnd]
Everything else SAME! π―
Critical Index Differences:
PREORDER:
root.left: pre[preStart+1 to preStart+leftSize]
root.right: pre[preStart+leftSize+1 to preEnd]
POSTORDER:
root.left: post[postStart to postStart+leftSize-1]
root.right: post[postStart+leftSize to postEnd-1]
Key: Right subtree ends at postEnd-1 (exclude root!)
The Postorder Structure:
postorder = [Left subtree, Right subtree, Root]
If leftSize = 2, postStart = 0, postEnd = 5:
Left: [0, 1] (2 elements)
Right: [2, 4] (remaining before root)
Root: [5] (last element)
Always: right ends at postEnd-1! π
Why Inorder is Essential:
Both preorder and postorder need inorder to:
- Find root position
- Split left vs right subtrees
- Determine subtree sizes
Inorder is the KEY to reconstruction! ποΈ
πͺ Memory Aid
"Postorder root at END, not start!"
"Right ends at postEnd-1 (skip root)!"
"Left: postStart to postStart+leftSize-1!"
"Same pattern as preorder, different indices!" β¨
β οΈ Don't Forget
- Root from postEnd (LAST, not first!)
- Right ends at postEnd-1 (exclude root!)
- Left ends at postStart+leftSize-1 (not +leftSize!)
- HashMap for O(1) (same optimization!)
- Same logic as Problem 149 (just different indices!)
- Inorder still splits left/right (unchanged!)
π Congratulations!
You've mastered both reconstruction patterns!
What You Learned:
β
Postorder properties - Root at end
β
Index arithmetic - Different from preorder
β
Pattern similarity - Same algorithm
β
Critical differences - Root location
β
Reconstruction mastery - Both variations
β
Problem comparison - Understanding differences
The Connection:
Problem 149 vs Problem 150:
SAME:
- Inorder for left/right split
- HashMap optimization
- leftSize calculation
- Divide and conquer
- O(n) complexity
DIFFERENT:
- Root position (start vs end)
- Index ranges for subtrees
- Array partitioning details
Learn one β Know both! π―
Interview Mastery:
Interviewer: "Build tree from inorder and postorder"
Your response:
"Very similar to preorder + inorder!
Key difference: Root at END of postorder
(Postorder: Left β Right β Root)
Algorithm:
1. Root = postorder[last]
2. Find root in inorder β split left/right
3. Calculate leftSize
4. Recursively build with correct ranges
Critical: Right ends at postEnd-1 (exclude root)
Time: O(n), Space: O(n)"
Shows pattern recognition! π―
You've mastered tree reconstruction from traversals! πβ¨π―