Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Hash Map, which you haven’t finished yet.

P2MediumBuilt on Bottom-Up DFS~4 min speedrun

Tree Construction (Build from Preorder + Inorder)

Built on
Bottom-Up DFSThe recursion works on index ranges of the two traversals, and the next preorder value is the root of each range.
The problem

Construct Binary Tree from Preorder and Inorder Traversal (LC 105)(opens leetcode.com)

A binary tree whose values are all different was walked twice, and each walk wrote its values into a list.

  • preorder lists every subtree as its root first, then its left subtree, then its right subtree.
  • inorder lists every subtree as its left subtree first, then its root, then its right subtree.

Both lists describe the same tree and hold the same values. Rebuild that tree and return its root.

Example 1
Input:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output:[3,9,20,null,null,15,7]
3 comes first in preorder, so it is the root. In inorder, only 9 sits left of 3, so 9 is the whole left subtree; 15, 20 and 7 sit right of it. Of those, 20 comes first in preorder, so it roots the right side, with 15 on its left and 7 on its right.
9315207

Construct Binary Tree

Spot it when: You get two traversal orders of one binary tree whose values are all distinct (preorder and inorder, inorder and postorder, or preorder and postorder, where more than one tree may fit) and must rebuild the tree.
Construct Binary Tree
Python
1from typing import Optional
2 
3class TreeNode:
4 def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None):
5 self.val = val
6 self.left = left
7 self.right = right
8 
9def buildTree(preorder: list[int], inorder: list[int]) -> Optional[TreeNode]:
10 """Construct Binary Tree from Preorder and Inorder Traversal: preorder names each root, inorder splits it.
11 Time: O(N) | Space: O(N) index map + O(H) recursion stack
12 """
13 where = {val: i for i, val in enumerate(inorder)} # Value -> its index in inorder, for O(1) splits
14 pre_idx = 0 # preorder[pre_idx] is the root of the next subtree to build
15 
16 def build(lo: int, hi: int) -> Optional[TreeNode]:
17 """Build the subtree whose values are inorder[lo..hi]."""
18 nonlocal pre_idx
19 if lo > hi:
20 return None # Empty range: no subtree here
21 root = TreeNode(preorder[pre_idx]) # Preorder lists every root before its subtrees
22 pre_idx += 1
23 mid = where[root.val] # inorder[lo..mid-1] is the left subtree, inorder[mid+1..hi] the right one
24 root.left = build(lo, mid - 1) # Left first: preorder lists the whole left subtree next
25 root.right = build(mid + 1, hi)
26 return root
27 
28 return build(0, len(inorder) - 1)
Invariant: build(lo, hi) builds exactly the values in inorder[lo..hi], and when it starts, preorder[pre_idx] is the root of that range.

Preorder writes every subtree as its root first, then its whole left subtree, then its whole right subtree, so the next unused value, preorder[pre_idx], is always the root of the subtree you are building. Inorder puts that root between its two subtrees, so mid = where[root.val] splits the range: inorder[lo..mid-1] is the left subtree and inorder[mid+1..hi] the right one. This is correct because the values are distinct, so mid is one exact index, and each call to build uses exactly one preorder value per node of its range; when build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree and points at the right subtree's root. The trap is the order of the two calls: root.left = build(lo, mid - 1) must run before root.right = build(mid + 1, hi), because pre_idx hands out roots in preorder; build the right side first and it takes the left subtree's root.

See it run

Building a tree from preorder and inorder, step by step

pre_idx 0 · range inorder[0..4]

pre_idx 1 · range inorder[0..4]

pre_idx 2 · range inorder[0..0]

pre_idx 2 · range empty

pre_idx 2 · range empty

pre_idx 3 · range inorder[2..4]

pre_idx 4 · range inorder[2..2]

pre_idx 4 · range empty

pre_idx 4 · range empty

pre_idx 5 · range inorder[4..4]

pre_idx 5 · range empty

pre_idx 5 · range empty

pre_idx 5 · answer [3, 9, 20, null, null, 15, 7]

Each circle is a tree node, drawn above its position in inorder. A node appears when its call of build creates it. The rows show preorder and inorder.

  • root the value preorder[pre_idx] just used as the root of this call
  • range the part of inorder that this call must build
  • built a node that already exists, or a preorder value already used

Start with pre_idx = 0 and the whole inorder range [0..4]. No node exists yet.

build(0, 4): preorder[0] = 3 is the root. Its index in inorder is 1, so the left side is inorder[0..0] and the right side is inorder[2..4].

build(0, 0): preorder[1] = 9 is the root. Its index in inorder is 0, so the left side is inorder[0..-1] and the right side is inorder[1..0].

build(0, -1): the range is empty, so there is no subtree. It returns None.

build(1, 0): the range is empty, so there is no subtree. It returns None.

build(2, 4): preorder[2] = 20 is the root. Its index in inorder is 3, so the left side is inorder[2..2] and the right side is inorder[4..4].

build(2, 2): preorder[3] = 15 is the root. Its index in inorder is 2, so the left side is inorder[2..1] and the right side is inorder[3..2].

build(2, 1): the range is empty, so there is no subtree. It returns None.

build(3, 2): the range is empty, so there is no subtree. It returns None.

build(4, 4): preorder[4] = 7 is the root. Its index in inorder is 4, so the left side is inorder[4..3] and the right side is inorder[5..4].

build(4, 3): the range is empty, so there is no subtree. It returns None.

build(5, 4): the range is empty, so there is no subtree. It returns None.

All calls have returned, and pre_idx = 5 equals the number of values. The tree in level order is [3, 9, 20, null, null, 15, 7].

preorder
3
9
20
15
7
inorder
9
3
15
20
7
1 / 13
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Building the right subtree before the left one

    Mistake
    root.right = build(mid + 1, hi)
    root.left = build(lo, mid - 1)
    Fix
    root.left = build(lo, mid - 1)
    root.right = build(mid + 1, hi)

    What happens: pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the next root after root belongs to the left subtree. If the right side is built first, it takes that root. On preorder = [3,9,20,15,7], the right subtree gets 9. The later calls then read past the end of preorder, and Python raises an IndexError.

    Example: Construct Binary Tree from Preorder and Inorder Traversal (LC 105): preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
  2. 2

    Gotcha 2: Passing pre_idx down as an argument

    Mistake
    root.left = build(lo, mid - 1, pre_idx + 1)
    root.right = build(mid + 1, hi, pre_idx + 1)
    Fix
    root.left = build(lo, mid - 1)
    root.right = build(mid + 1, hi)

    What happens: pre_idx must be one counter that every call shares, through nonlocal. The right subtree's root comes after the whole left subtree in preorder. An argument does not know how many values the left call used. So the right call starts right after its parent, as if the left subtree were empty. In the example, the calls then read past the end of preorder, and Python raises an IndexError.

    Example: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
  3. 3

    Gotcha 3: Calling inorder.index(root.val) in every call

    Mistake
    mid = inorder.index(root.val)
    Fix
    mid = where[root.val]

    What happens: inorder.index scans the list from the start on every call. On a tree shaped like a chain, that costs O(N^2) time in total. where is built once, and each lookup takes O(1) time. The answer is the same, only slower.

  4. 4

    Gotcha 4: Writing lo >= hi as the empty test

    Mistake
    if lo >= hi:
    Fix
    if lo > hi:

    What happens: lo == hi is a range of one value, which is a leaf. With lo >= hi, every leaf returns None without using its preorder value. pre_idx then stays behind, and every later root is taken from the wrong position.

    Example: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: returns [3, None, 9, None, 20, 15]Fix: returns [3, 9, 20, None, None, 15, 7]
Not this one if…
  • You get only one list, the preorder of a binary search tree, as in Construct Binary Search Tree from Preorder Traversal (LC 1008): the BST order takes the place of inorder, so each value is placed by the (low, high) window from its ancestors, not split by its index in a second list. See BST Bounds

Deep Dive

Why preorder names the root and inorder splits it

Preorder visits a node first, then its whole left subtree, then its whole right subtree. So the first value of any subtree is its root. Inside one call of build, the next unused value preorder[pre_idx] is the root of the subtree that call must build.

Inorder visits the left subtree, then the node, then the right subtree. So the root sits between its two subtrees. Because all values are distinct, mid = where[root.val] is one exact index. The values inorder[lo..mid-1] are the left subtree, and the values inorder[mid+1..hi] are the right subtree.

Each call uses exactly one value of preorder for each node in its range. When build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree. It now points at the root of the right subtree, which is what build(mid + 1, hi) needs.

Example: in Construct Binary Tree from Preorder and Inorder Traversal (LC 105), preorder = [3, 9, 20, 15, 7] and inorder = [9, 3, 15, 20, 7] give the tree [3, 9, 20, null, null, 15, 7]. The root is 3. Inorder has [9] on its left and [15, 20, 7] on its right.

One trace: the calls and pre_idx

The board replays the example. Each node is drawn above its index in inorder, so the range of a subtree is always a block of neighbours. A node appears when its call creates it.

build(0, 4) uses preorder[0] = 3. Its index in inorder is 1, so the left range is [0..0] and the right range is [2..4]. The left call uses preorder[1] = 9, whose range [0..0] splits into two empty ranges. Then pre_idx is 2.

The right call build(2, 4) now reads preorder[2] = 20. This is correct only because the left call has used exactly the values of its own subtree. Then 15 and 7 are the roots of the two ranges inside [2..4]. At the end, pre_idx is 5, which is the number of values.

For 5 nodes there are 11 calls: 5 calls create a node and 6 calls get an empty range. A tree with N nodes always has N calls that create a node and N + 1 calls with an empty range.

The trap: the order of the two calls

pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the left call must run first.

Suppose the right call runs first. For the example, build(0, 4) creates the root 3, and then build(2, 4) takes preorder[1] = 9. The value 9 is at inorder index 0, which is outside the range [2..4]. The call builds a wrong node, and the later calls read past the end of preorder. Python raises an IndexError.

pre_idx must also be one shared counter. If it is passed down as an argument, the right call starts right after its parent, as if the left subtree were empty. The call has no way to know how many values the left call used.

The empty test must be lo > hi. The range lo == hi has one value, which is a leaf. With lo >= hi, every leaf returns None and does not use its preorder value. On the example, the result is [3, null, 9, null, 20, 15], which is wrong.

The cost, and the neighbouring cards

The dictionary where is built once, so each split costs O(1). Each of the N values makes one node and two calls, so the time is O(N). The space is O(N) for where, plus O(H) for the recursion stack, where H is the height of the tree.

inorder.index(root.val) gives the same answer, but each call scans the list. For a tree shaped like a chain of 1000 nodes, the scans compare 500500 values in total, and the dictionary does 1000 lookups.

Inorder and postorder is the same idea, as in Construct Binary Tree from Inorder and Postorder Traversal (LC 106). Postorder writes the root last, so read it from the end, and build the right subtree first. Preorder and postorder, as in Construct Binary Tree from Preorder and Postorder Traversal (LC 889), do not fix one tree in general, so that problem has extra rules.

With one list only, the preorder of a binary search tree, the sorted order replaces inorder. Then the idea of the Bounds card applies, as in Construct Binary Search Tree from Preorder Traversal (LC 1008).

60-Second Pitch

"Preorder gives me the root of every subtree, and inorder tells me which values are on its left and which are on its right. I keep a map from each value to its inorder index. I keep one shared counter pre_idx into preorder. build(lo, hi) returns None for an empty range. Otherwise it takes preorder[pre_idx] as the root and moves the counter forward. It then finds mid from the map, builds the left range lo..mid-1 first, and the right range mid+1..hi second. The left call must run first, because preorder lists the whole left subtree before the right subtree. The time is O(N) with the map, and the space is O(N) plus the recursion stack of height H."

Common Questions

Why must the left subtree be built before the right subtree?

Preorder writes the root, then the whole left subtree, then the whole right subtree. The shared counter pre_idx moves through preorder from left to right. After the root, the next values belong to the left subtree. If the right call ran first, it would take the left subtree's root.

Why do you need distinct values?

The map where gives one index for each value. With a repeated value, the root could match more than one place in inorder, so the split is not fixed. The problem promises distinct values, so each split is exact.

What are the time and space complexity?

O(N) time, because each value creates one node and makes two calls, and each split is a map lookup. O(N) space for the map, plus O(H) for the recursion stack. For a chain-shaped tree, H is N.

How do you change it for inorder and postorder?

Postorder writes the root last, so start post_idx at the end and move it backwards. The right subtree comes just before the root in postorder, so build the right range first and the left range second. This is Construct Binary Tree from Inorder and Postorder Traversal (LC 106).

Can preorder and postorder give a unique tree?

Not in general. If a node has only one child, preorder and postorder do not tell whether it is the left child or the right child. That is why Construct Binary Tree from Preorder and Postorder Traversal (LC 889) accepts any valid answer.

SWE Reasoning

Spot it

  • The input gives two traversal orders of one binary tree, and the task is to rebuild the tree.
  • The values are distinct, so every value has one exact place in the inorder list.
  • One of the orders names the root of each subtree (preorder first, postorder last), and inorder splits the left side from the right side.
  • A recursive build works on a range of one list, and the other list is read with one shared counter.
  • The wording says construct, rebuild or restore a tree from its traversals.

The invariant, step by step

  1. where maps each value to its index in inorder. Because values are distinct, each value has one index.
  2. build(lo, hi) builds exactly the values inorder[lo..hi]. If lo > hi, there is nothing to build, so it returns None.
  3. When the call starts, preorder[pre_idx] is the root of that range, because preorder lists a root before all of its subtree.
  4. The call takes the root, moves pre_idx forward, and finds mid = where[root.val]. The left subtree is inorder[lo..mid-1] and the right subtree is inorder[mid+1..hi].
  5. The left call runs first. It uses exactly the preorder values of the left subtree, so pre_idx then points at the root of the right subtree.
  6. The right call runs second and does the same. Every value is used once, and the whole tree is built.

Traps

  • The right call before the left call. It takes the left subtree's root. The result is a wrong tree or an IndexError.
  • pre_idx passed as an argument. Each call needs to know how many values the other call used. Share one counter with nonlocal.
  • lo >= hi as the empty test. A range with one value is a leaf. It must use its preorder value.
  • inorder.index(root.val) in every call. The answer is the same, but a chain-shaped tree costs O(N^2). Build where once.
  • Repeated values. With duplicates, where[root.val] is not one exact index, and the tree is not fixed. The problem promises distinct values.

Practice

Canonical LeetCode:Construct Binary Tree from Preorder and Inorder Traversal (LC 105)Construct Binary Tree from Inorder and Postorder Traversal (LC 106)Construct Binary Tree from Preorder and Postorder Traversal (LC 889)
Theory Context•Depth-First Search (DFS)

Builds on Bottom-Up DFS and Hash Map, which you haven’t finished yet.

P2MediumBuilt on Bottom-Up DFS~4 min speedrun

Tree Construction (Build from Preorder + Inorder)

Built on
Bottom-Up DFSThe recursion works on index ranges of the two traversals, and the next preorder value is the root of each range.
The problem

Construct Binary Tree from Preorder and Inorder Traversal (LC 105)(opens leetcode.com)

A binary tree whose values are all different was walked twice, and each walk wrote its values into a list.

  • preorder lists every subtree as its root first, then its left subtree, then its right subtree.
  • inorder lists every subtree as its left subtree first, then its root, then its right subtree.

Both lists describe the same tree and hold the same values. Rebuild that tree and return its root.

Example 1
Input:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output:[3,9,20,null,null,15,7]
3 comes first in preorder, so it is the root. In inorder, only 9 sits left of 3, so 9 is the whole left subtree; 15, 20 and 7 sit right of it. Of those, 20 comes first in preorder, so it roots the right side, with 15 on its left and 7 on its right.
9315207

Construct Binary Tree

Spot it when: You get two traversal orders of one binary tree whose values are all distinct (preorder and inorder, inorder and postorder, or preorder and postorder, where more than one tree may fit) and must rebuild the tree.
Construct Binary Tree
Python
1from typing import Optional
2 
3class TreeNode:
4 def __init__(self, val: int = 0, left: Optional["TreeNode"] = None, right: Optional["TreeNode"] = None):
5 self.val = val
6 self.left = left
7 self.right = right
8 
9def buildTree(preorder: list[int], inorder: list[int]) -> Optional[TreeNode]:
10 """Construct Binary Tree from Preorder and Inorder Traversal: preorder names each root, inorder splits it.
11 Time: O(N) | Space: O(N) index map + O(H) recursion stack
12 """
13 where = {val: i for i, val in enumerate(inorder)} # Value -> its index in inorder, for O(1) splits
14 pre_idx = 0 # preorder[pre_idx] is the root of the next subtree to build
15 
16 def build(lo: int, hi: int) -> Optional[TreeNode]:
17 """Build the subtree whose values are inorder[lo..hi]."""
18 nonlocal pre_idx
19 if lo > hi:
20 return None # Empty range: no subtree here
21 root = TreeNode(preorder[pre_idx]) # Preorder lists every root before its subtrees
22 pre_idx += 1
23 mid = where[root.val] # inorder[lo..mid-1] is the left subtree, inorder[mid+1..hi] the right one
24 root.left = build(lo, mid - 1) # Left first: preorder lists the whole left subtree next
25 root.right = build(mid + 1, hi)
26 return root
27 
28 return build(0, len(inorder) - 1)
Invariant: build(lo, hi) builds exactly the values in inorder[lo..hi], and when it starts, preorder[pre_idx] is the root of that range.

Preorder writes every subtree as its root first, then its whole left subtree, then its whole right subtree, so the next unused value, preorder[pre_idx], is always the root of the subtree you are building. Inorder puts that root between its two subtrees, so mid = where[root.val] splits the range: inorder[lo..mid-1] is the left subtree and inorder[mid+1..hi] the right one. This is correct because the values are distinct, so mid is one exact index, and each call to build uses exactly one preorder value per node of its range; when build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree and points at the right subtree's root. The trap is the order of the two calls: root.left = build(lo, mid - 1) must run before root.right = build(mid + 1, hi), because pre_idx hands out roots in preorder; build the right side first and it takes the left subtree's root.

See it run

Building a tree from preorder and inorder, step by step

pre_idx 0 · range inorder[0..4]

pre_idx 1 · range inorder[0..4]

pre_idx 2 · range inorder[0..0]

pre_idx 2 · range empty

pre_idx 2 · range empty

pre_idx 3 · range inorder[2..4]

pre_idx 4 · range inorder[2..2]

pre_idx 4 · range empty

pre_idx 4 · range empty

pre_idx 5 · range inorder[4..4]

pre_idx 5 · range empty

pre_idx 5 · range empty

pre_idx 5 · answer [3, 9, 20, null, null, 15, 7]

Each circle is a tree node, drawn above its position in inorder. A node appears when its call of build creates it. The rows show preorder and inorder.

  • root the value preorder[pre_idx] just used as the root of this call
  • range the part of inorder that this call must build
  • built a node that already exists, or a preorder value already used

Start with pre_idx = 0 and the whole inorder range [0..4]. No node exists yet.

build(0, 4): preorder[0] = 3 is the root. Its index in inorder is 1, so the left side is inorder[0..0] and the right side is inorder[2..4].

build(0, 0): preorder[1] = 9 is the root. Its index in inorder is 0, so the left side is inorder[0..-1] and the right side is inorder[1..0].

build(0, -1): the range is empty, so there is no subtree. It returns None.

build(1, 0): the range is empty, so there is no subtree. It returns None.

build(2, 4): preorder[2] = 20 is the root. Its index in inorder is 3, so the left side is inorder[2..2] and the right side is inorder[4..4].

build(2, 2): preorder[3] = 15 is the root. Its index in inorder is 2, so the left side is inorder[2..1] and the right side is inorder[3..2].

build(2, 1): the range is empty, so there is no subtree. It returns None.

build(3, 2): the range is empty, so there is no subtree. It returns None.

build(4, 4): preorder[4] = 7 is the root. Its index in inorder is 4, so the left side is inorder[4..3] and the right side is inorder[5..4].

build(4, 3): the range is empty, so there is no subtree. It returns None.

build(5, 4): the range is empty, so there is no subtree. It returns None.

All calls have returned, and pre_idx = 5 equals the number of values. The tree in level order is [3, 9, 20, null, null, 15, 7].

preorder
3
9
20
15
7
inorder
9
3
15
20
7
1 / 13
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Building the right subtree before the left one

    Mistake
    root.right = build(mid + 1, hi)
    root.left = build(lo, mid - 1)
    Fix
    root.left = build(lo, mid - 1)
    root.right = build(mid + 1, hi)

    What happens: pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the next root after root belongs to the left subtree. If the right side is built first, it takes that root. On preorder = [3,9,20,15,7], the right subtree gets 9. The later calls then read past the end of preorder, and Python raises an IndexError.

    Example: Construct Binary Tree from Preorder and Inorder Traversal (LC 105): preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
  2. 2

    Gotcha 2: Passing pre_idx down as an argument

    Mistake
    root.left = build(lo, mid - 1, pre_idx + 1)
    root.right = build(mid + 1, hi, pre_idx + 1)
    Fix
    root.left = build(lo, mid - 1)
    root.right = build(mid + 1, hi)

    What happens: pre_idx must be one counter that every call shares, through nonlocal. The right subtree's root comes after the whole left subtree in preorder. An argument does not know how many values the left call used. So the right call starts right after its parent, as if the left subtree were empty. In the example, the calls then read past the end of preorder, and Python raises an IndexError.

    Example: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: raises IndexErrorFix: returns [3, 9, 20, None, None, 15, 7]
  3. 3

    Gotcha 3: Calling inorder.index(root.val) in every call

    Mistake
    mid = inorder.index(root.val)
    Fix
    mid = where[root.val]

    What happens: inorder.index scans the list from the start on every call. On a tree shaped like a chain, that costs O(N^2) time in total. where is built once, and each lookup takes O(1) time. The answer is the same, only slower.

  4. 4

    Gotcha 4: Writing lo >= hi as the empty test

    Mistake
    if lo >= hi:
    Fix
    if lo > hi:

    What happens: lo == hi is a range of one value, which is a leaf. With lo >= hi, every leaf returns None without using its preorder value. pre_idx then stays behind, and every later root is taken from the wrong position.

    Example: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
    Mistake: returns [3, None, 9, None, 20, 15]Fix: returns [3, 9, 20, None, None, 15, 7]
Not this one if…
  • You get only one list, the preorder of a binary search tree, as in Construct Binary Search Tree from Preorder Traversal (LC 1008): the BST order takes the place of inorder, so each value is placed by the (low, high) window from its ancestors, not split by its index in a second list. See BST Bounds

Deep Dive

Why preorder names the root and inorder splits it

Preorder visits a node first, then its whole left subtree, then its whole right subtree. So the first value of any subtree is its root. Inside one call of build, the next unused value preorder[pre_idx] is the root of the subtree that call must build.

Inorder visits the left subtree, then the node, then the right subtree. So the root sits between its two subtrees. Because all values are distinct, mid = where[root.val] is one exact index. The values inorder[lo..mid-1] are the left subtree, and the values inorder[mid+1..hi] are the right subtree.

Each call uses exactly one value of preorder for each node in its range. When build(lo, mid - 1) returns, pre_idx has moved past the whole left subtree. It now points at the root of the right subtree, which is what build(mid + 1, hi) needs.

Example: in Construct Binary Tree from Preorder and Inorder Traversal (LC 105), preorder = [3, 9, 20, 15, 7] and inorder = [9, 3, 15, 20, 7] give the tree [3, 9, 20, null, null, 15, 7]. The root is 3. Inorder has [9] on its left and [15, 20, 7] on its right.

One trace: the calls and pre_idx

The board replays the example. Each node is drawn above its index in inorder, so the range of a subtree is always a block of neighbours. A node appears when its call creates it.

build(0, 4) uses preorder[0] = 3. Its index in inorder is 1, so the left range is [0..0] and the right range is [2..4]. The left call uses preorder[1] = 9, whose range [0..0] splits into two empty ranges. Then pre_idx is 2.

The right call build(2, 4) now reads preorder[2] = 20. This is correct only because the left call has used exactly the values of its own subtree. Then 15 and 7 are the roots of the two ranges inside [2..4]. At the end, pre_idx is 5, which is the number of values.

For 5 nodes there are 11 calls: 5 calls create a node and 6 calls get an empty range. A tree with N nodes always has N calls that create a node and N + 1 calls with an empty range.

The trap: the order of the two calls

pre_idx hands out roots in preorder order. Preorder lists the whole left subtree before the right subtree. So the left call must run first.

Suppose the right call runs first. For the example, build(0, 4) creates the root 3, and then build(2, 4) takes preorder[1] = 9. The value 9 is at inorder index 0, which is outside the range [2..4]. The call builds a wrong node, and the later calls read past the end of preorder. Python raises an IndexError.

pre_idx must also be one shared counter. If it is passed down as an argument, the right call starts right after its parent, as if the left subtree were empty. The call has no way to know how many values the left call used.

The empty test must be lo > hi. The range lo == hi has one value, which is a leaf. With lo >= hi, every leaf returns None and does not use its preorder value. On the example, the result is [3, null, 9, null, 20, 15], which is wrong.

The cost, and the neighbouring cards

The dictionary where is built once, so each split costs O(1). Each of the N values makes one node and two calls, so the time is O(N). The space is O(N) for where, plus O(H) for the recursion stack, where H is the height of the tree.

inorder.index(root.val) gives the same answer, but each call scans the list. For a tree shaped like a chain of 1000 nodes, the scans compare 500500 values in total, and the dictionary does 1000 lookups.

Inorder and postorder is the same idea, as in Construct Binary Tree from Inorder and Postorder Traversal (LC 106). Postorder writes the root last, so read it from the end, and build the right subtree first. Preorder and postorder, as in Construct Binary Tree from Preorder and Postorder Traversal (LC 889), do not fix one tree in general, so that problem has extra rules.

With one list only, the preorder of a binary search tree, the sorted order replaces inorder. Then the idea of the Bounds card applies, as in Construct Binary Search Tree from Preorder Traversal (LC 1008).

60-Second Pitch

"Preorder gives me the root of every subtree, and inorder tells me which values are on its left and which are on its right. I keep a map from each value to its inorder index. I keep one shared counter pre_idx into preorder. build(lo, hi) returns None for an empty range. Otherwise it takes preorder[pre_idx] as the root and moves the counter forward. It then finds mid from the map, builds the left range lo..mid-1 first, and the right range mid+1..hi second. The left call must run first, because preorder lists the whole left subtree before the right subtree. The time is O(N) with the map, and the space is O(N) plus the recursion stack of height H."

Common Questions

Why must the left subtree be built before the right subtree?

Preorder writes the root, then the whole left subtree, then the whole right subtree. The shared counter pre_idx moves through preorder from left to right. After the root, the next values belong to the left subtree. If the right call ran first, it would take the left subtree's root.

Why do you need distinct values?

The map where gives one index for each value. With a repeated value, the root could match more than one place in inorder, so the split is not fixed. The problem promises distinct values, so each split is exact.

What are the time and space complexity?

O(N) time, because each value creates one node and makes two calls, and each split is a map lookup. O(N) space for the map, plus O(H) for the recursion stack. For a chain-shaped tree, H is N.

How do you change it for inorder and postorder?

Postorder writes the root last, so start post_idx at the end and move it backwards. The right subtree comes just before the root in postorder, so build the right range first and the left range second. This is Construct Binary Tree from Inorder and Postorder Traversal (LC 106).

Can preorder and postorder give a unique tree?

Not in general. If a node has only one child, preorder and postorder do not tell whether it is the left child or the right child. That is why Construct Binary Tree from Preorder and Postorder Traversal (LC 889) accepts any valid answer.

SWE Reasoning

Spot it

  • The input gives two traversal orders of one binary tree, and the task is to rebuild the tree.
  • The values are distinct, so every value has one exact place in the inorder list.
  • One of the orders names the root of each subtree (preorder first, postorder last), and inorder splits the left side from the right side.
  • A recursive build works on a range of one list, and the other list is read with one shared counter.
  • The wording says construct, rebuild or restore a tree from its traversals.

The invariant, step by step

  1. where maps each value to its index in inorder. Because values are distinct, each value has one index.
  2. build(lo, hi) builds exactly the values inorder[lo..hi]. If lo > hi, there is nothing to build, so it returns None.
  3. When the call starts, preorder[pre_idx] is the root of that range, because preorder lists a root before all of its subtree.
  4. The call takes the root, moves pre_idx forward, and finds mid = where[root.val]. The left subtree is inorder[lo..mid-1] and the right subtree is inorder[mid+1..hi].
  5. The left call runs first. It uses exactly the preorder values of the left subtree, so pre_idx then points at the root of the right subtree.
  6. The right call runs second and does the same. Every value is used once, and the whole tree is built.

Traps

  • The right call before the left call. It takes the left subtree's root. The result is a wrong tree or an IndexError.
  • pre_idx passed as an argument. Each call needs to know how many values the other call used. Share one counter with nonlocal.
  • lo >= hi as the empty test. A range with one value is a leaf. It must use its preorder value.
  • inorder.index(root.val) in every call. The answer is the same, but a chain-shaped tree costs O(N^2). Build where once.
  • Repeated values. With duplicates, where[root.val] is not one exact index, and the tree is not fixed. The problem promises distinct values.

Practice

Canonical LeetCode:Construct Binary Tree from Preorder and Inorder Traversal (LC 105)Construct Binary Tree from Inorder and Postorder Traversal (LC 106)Construct Binary Tree from Preorder and Postorder Traversal (LC 889)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•