DSA sheet · Trees · Construction pattern

Build Tree from Inorder & Postorder

This is the twin of the previous problem (Build Tree from Preorder & Inorder), and the teacher treats that video as a must-watch first. Inorder still does the splitting. The difference is that the root now comes from postorder, where the root is written last. That one fact causes two small changes: read postorder from the back, and build the right child before the left child. Understanding why the second change is needed is the real lesson of this video.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Quick recap of the three orders

nameorderwhere the root sits
preorderroot → left → rightfirst
inorderleft → root → rightin the middle (left part before it, right part after it)
postorderleft → right → rootlast

A subtree is a node plus everything below it. A window left..right is a range of indexes in the inorder list; each recursive call may only use the nodes in its window. left > right means the window is empty.

What we keep from the previous problem

The example tree for this page

        3
       / \
      9   20
     /   /  \
    4   15   7

inorder   = [4, 9, 3, 15, 20, 7]     (left, root, right)
postorder = [4, 9, 15, 7, 20, 3]     (left, right, root)

Part A · Build the tree with a hashmap (and right-first calls)

LeetCode 106 · Construct Binary Tree from Inorder and Postorder Traversal

1The question in simple words

You get inorder and postorder of the same binary tree. Rebuild the tree and return its root.

input:  inorder   = [4, 9, 3, 15, 20, 7]
        postorder = [4, 9, 15, 7, 20, 3]

output: the root of        3
                          / \
                         9   20
                        /   /  \
                       4   15   7

2What the constraints tell us

3Intuition: how to think about it

The splitting picture is the same as before: point at the root in inorder, the left people are the left subtree, the right people are the right subtree.

The new question is only "where do I read the root from?" Postorder writes the root after both of its subtrees, so the last value of postorder is the root of the whole tree. Here that's 3.

Now read postorder backwards: 3, 20, 7, 15, 9, 4. That's root, then the right subtree's nodes, then the left subtree's nodes. Reading postorder from the back is like reading a preorder that visits right before left. That's the whole reason the right child must be built first.

postorder = [ 4   9 | 15   7  20 | 3 ]
              -----   ----------   ^
              left      right     root      (read it from the back:
                                             root, then right, then left)

4Building the logic from the example

Step 1 · The root is the last value of postorder

Postorder is "left, right, root", so the root of the whole tree is postorder[n - 1]. In preorder we started at index 0 and moved forward (+= 1). Here we start at the last index and move backward (-= 1). The teacher sets post_idx = n - 1, and since both lists have the same length, len(inorder) - 1 and len(postorder) - 1 are the same number.

Step 2 · Split with inorder (unchanged)

pos[3] = 2. Indexes 0..1 (4, 9) are the left subtree; indexes 3..5 (15, 20, 7) are the right subtree.

Doubt 1: why mid - 1 and mid + 1, and not mid?
→ The cell at mid is the root we just made. It belongs to neither subtree. If we included it, the same value would be made into a node twice. So the left window is left..mid-1 and the right window is mid+1..right.

Step 3 · The next root belongs to the RIGHT side

After making 3, the pointer moves back one step to 20. In the preorder problem, the value after the root was the root of the left subtree. But look at the picture above: in postorder, the value just before the root is the root of the right subtree. 20 can't be the left child here; it's the right child.

So the pointer will hand out the right side's nodes first. If we call for the left child first, the left window (which holds only 4 and 9) would be handed 20. Therefore:

The one big changeroot.right = build(mid + 1, right) first,
then root.left = build(left, mid - 1).
Doubt 2: what actually goes wrong if I keep left first, like in the previous problem?
→ Try it on the example. Call 1 makes 3 (pointer → 20). Call 2 is the left window 0..1, and it makes 20, a node that isn't even in that window. pos[20] = 4, so the next windows become 0..3 and 5..1, which are nonsense. The pointer keeps falling and goes below 0. Python doesn't stop you at −1 (a negative index silently reads from the end of the list), so wrong nodes keep getting made until it finally crashes with an IndexError. One swapped line breaks everything.

Step 4 · Base case and return (unchanged)

The teacher's reminder: in tree or linked-list problems we usually stop when a node is None, but here we're creating nodes, not walking them, so the stop rule must use the indexes.

Step 5 · The hashmap and the shared pointer

Doubt 3: could I find the root's index by scanning inorder instead of using a map?
→ Yes, because the values are unique. But that scan is O(n) for each of n nodes → O(n²). The teacher covers this in the previous video and goes straight to the map here. (The scanning code is in Part A of the previous page; only the pointer direction and the call order differ.)

5Approach steps

  1. Fill pos[inorder[i]] = i for every index i.
  2. Set post_idx = n - 1 (the last index of postorder).
  3. Call build(0, n - 1) and return what it gives.
  4. In build: if left > right → return None.
  5. Make a node from postorder[post_idx], then post_idx -= 1.
  6. mid = pos[node value].
  7. Right first: root.right = build(mid + 1, right). Then root.left = build(left, mid - 1).
  8. Return the node.

6Code (Python)

Inorder + Postorder with a hashmap
class Solution:
    def buildTree(self, inorder, postorder):
        self.pos = {}                              # value -> index in inorder
        for i in range(len(inorder)):
            self.pos[inorder[i]] = i
        self.post_idx = len(postorder) - 1         # start at the LAST value
        return self.build(postorder, 0, len(inorder) - 1)

    def build(self, postorder, left, right):
        if left > right:                           # window crossed: no node
            return None
        root = TreeNode(postorder[self.post_idx])
        self.post_idx -= 1                         # move BACKWARD

        mid = self.pos[root.val]
        root.right = self.build(postorder, mid + 1, right)   # right FIRST
        root.left = self.build(postorder, left, mid - 1)     # then left
        return root

7Code line by line

linewhat it means
self.pos = {} for i in range(len(inorder)): self.pos[inorder[i]] = iOne pass over inorder: remember where each value sits, so later look-ups are O(1).
self.post_idx = len(postorder) - 1The root of the whole tree is the last postorder value, so the pointer starts there. Changed from 0.
self.build(postorder, 0, len(inorder) - 1)The top call may use the whole inorder range. Inorder isn't passed; the map replaces it.
if left > right: return NoneEmpty window → no child here. Stops the recursion.
root = TreeNode(postorder[self.post_idx]) self.post_idx -= 1Make this window's root, then step the pointer back one place. Changed from += 1.
mid = self.pos[root.val]Where the root sits in inorder.
root.right = self.build(postorder, mid + 1, right)The part after the root is the right subtree. Done first, because the backward pointer reaches the right subtree's nodes first. Swapped.
root.left = self.build(postorder, left, mid - 1)The part before the root is the left subtree. By now the pointer has passed all the right-side nodes.
return rootGive the finished subtree back to the parent.
If your code failsThe teacher's first run also failed on a small missed detail, which she fixed on the spot. Check these first: does post_idx start at n - 1? Does it go down? Is the right call above the left call?

8Dry run: watch the windows split

inorder   = [4, 9, 3, 15, 20, 7]         pos = { 4:0, 9:1, 3:2, 15:3, 20:4, 7:5 }
postorder = [4, 9, 15, 7, 20, 3]         post_idx starts at 5

Boxes show the inorder list. Yellow = this window's root, plain = inside the window, grey = outside it.

call 149315207build(0, 5): post_idx 5 → 3 (idx → 4); pos[3] = 2 → right (3, 5) first, left (0, 1) later
call 249315207build(3, 5): post_idx 4 → 20 (idx → 3); pos[20] = 4 → right (5, 5) first
call 349315207build(5, 5): post_idx 3 → 7 (idx → 2); right (6, 5) → None, left (5, 4) → None → return 7 → 20.right = 7
call 449315207back in 20, left (3, 3): post_idx 2 → 15 (idx → 1); children (4, 3), (3, 2) → None, None → 20.left = 15
20 is complete → returns → 3.right = 20
call 549315207back in 3, left (0, 1): post_idx 1 → 9 (idx → 0); pos[9] = 1 → right (2, 1) → None
call 6493152079's left (0, 0): post_idx 0 → 4 (idx → −1); children (1, 0), (0, −1) → None, None → 9.left = 4
end9 returns → 3.left = 9. Call 1 returns 3. post_idx = −1: every value used exactly once ✓

Nodes were made in the order 3, 20, 7, 15, 9, 4, which is postorder read from the back. The right side of every node was finished before its left side, exactly as the pointer needed.

stack at call 3 (deepest on the right)
build(0,5) node 3build(3,5) node 20build(5,5) node 7
stack at call 6
build(0,5) node 3build(0,1) node 9build(0,0) node 4
after the end
empty → return 3

The teacher's picture: the nodes are created going down, but they are only joined into a tree as each call returns its root upward. 7 and 15 join 20, 20 joins 3, then 4 joins 9 and 9 joins 3.

9Complexity & remember

Doubt 4: RecursionError on a 3000-node straight-line tree when I test locally?
→ The calls go 3000 deep and plain Python stops at about 1000. Add import sys; sys.setrecursionlimit(10000) when testing on your machine. The algorithm is fine.
RememberSame as preorder + inorder, except: pointer starts at the end and goes down, and the right child is built before the left.

Part B · Revision page

Preorder + Inorder (105)Inorder + Postorder (106)
root comes frompreorder, front to backpostorder, back to front
pointer start0n - 1
pointer step+= 1-= 1
call orderleft, then rightright, then left
splitinorder via pos[value]: left window left..mid-1, right window mid+1..right
base caseleft > right → None
time / spaceO(n) / O(n) (map) + O(h) stack
read this listyou see
preorder forwardsroot, left subtree, right subtree
postorder backwardsroot, right subtree, left subtree
If you remember only 5 lines 1. Postorder's last value is the root.
2. Inorder splits the remaining nodes into left and right windows.
3. Map inorder values to indexes once → O(1) look-ups → O(n) total.
4. Pointer starts at n - 1 and moves down.
5. Build right before left, because postorder read backwards lists the right subtree first.
Mistakes to avoid ✗ copying the 105 code and keeping left-first (wrong nodes go into the wrong windows)
✗ starting the pointer at 0, or doing += 1
✗ including mid in a child window
✗ passing the pointer as a parameter instead of sharing it on self
✗ forgetting return root
test it yourself (paste under the solution above)
def inorder_of(node):
    return inorder_of(node.left) + [node.val] + inorder_of(node.right) if node else []

def postorder_of(node):
    return postorder_of(node.left) + postorder_of(node.right) + [node.val] if node else []

ino = [4, 9, 3, 15, 20, 7]
post = [4, 9, 15, 7, 20, 3]
root = Solution().buildTree(ino, post)
print(root.val, root.left.val, root.right.val)               # 3 9 20
print(inorder_of(root) == ino, postorder_of(root) == post)   # True True
print(Solution().buildTree([1], [1]).val)                    # 1

Based on this video: Construct Binary Tree from Inorder and Postorder Traversal