latency.lab
Algorithms · Topic 07

Trees

Level: foundationTool: recursive decomposition, BFS vs. DFSTime: ~65 min

By the end you can

Every problem in this topic is really the same question in disguise: "what does this node need from its children, and what does it hand back to its parent?" Once you can answer that for a specific problem, the recursion almost writes itself — the hard part is figuring out exactly what that handoff should contain, not the recursive mechanics themselves.

The core mental model: trust the recursion

Invert Binary Tree and Maximum Depth are the simplest possible cases: a node's answer is a small combination of its children's answers (swap them; take the max and add one), and the base case (an empty tree) is trivial. Don't try to trace through the whole tree by hand — define what a node needs from its children, write that as the recursive step, get the base case right, and let recursion handle the rest.

The habit to build

Before coding, finish this sentence: "given the answer for my left subtree and my answer for my right subtree, my own answer is ___." If you can't fill in that blank, you don't have a plan yet — Binary Tree Maximum Path Sum is hard precisely because that blank has two different answers (what this node returns upward vs. what it contributes to the running best) living in the same recursive call.

BFS vs. DFS: pick the traversal the problem is actually asking for

Binary Tree Level Order Traversal and Right Side View both want information organized by depth — that's breadth-first search with a queue, not recursion. The trick that makes level-by-level BFS clean: record the queue's size at the start of each iteration before processing anything — that number is exactly how many nodes belong to the current level, even though their children get pushed onto the same queue during that same pass.

Kth Smallest Element in a BST wants values in sorted order — that's an inorder traversal (left, node, right), which is the one traversal order that visits a BST's values from smallest to largest, for free, with no sorting step at all.

5 4th 3 2nd 8 5th 1 1st 4 3rd
Inorder traversal (left, node, right) on this BST visits 1, 3, 4, 5, 8 in that order — already sorted, with no separate sorting step. That's the one traversal order guaranteed to read a BST's values out ascending.

The BST property is a global constraint, not a local one

Validate Binary Search Tree's classic trap: checking a node only against its immediate parent passes plenty of genuinely invalid trees. A node in the left subtree of some ancestor must stay below that ancestor's value too, not just its direct parent's — the fix is threading a valid (low, high) range through the recursion, tightening one side at each step down.

why-local-checks-arent-enough.cpp
// WRONG: only compares each node to its immediate parent.
// Passes a tree where a deep-left node is greater than an ANCESTOR
// several levels up, even though it's only compared against its parent.
bool check(TreeNode* node) {
    if (node->left && node->left->val >= node->val) return false;
    if (node->right && node->right->val <= node->val) return false;
    return check(node->left) && check(node->right);
}

// RIGHT: every node carries a valid (low, high) range from its ancestors,
// not just a comparison against its immediate parent.
bool validate(TreeNode* node, long long lo, long long hi) {
    if (!node) return true;
    if (node->val <= lo || node->val >= hi) return false;
    return validate(node->left, lo, node->val) && validate(node->right, node->val, hi);
}

What a BST's ordering buys you: O(h) instead of O(n)

Lowest Common Ancestor of a BST and Lowest Common Ancestor of a Binary Tree solve the exact same problem, but the BST version only needs O(h) time (h = tree height) because comparing values against the current node tells you which single subtree to descend into — no need to search both sides the way the general-tree version genuinely must. Recognizing when a data structure's extra guarantee (sortedness, here) unlocks a cheaper algorithm — rather than defaulting to the general-case approach out of habit — is the same skill this course has built since Binary Search.

Common mistakes

  • Confusing "return value" with "answer" in problems like Maximum Path Sum — what a node hands its parent (a single extendable branch) is not always the same thing as the best answer seen so far (which is allowed to use both branches at once, since it won't be extended further).
  • Checking a BST node only against its immediate parent instead of the full valid range inherited from every ancestor.
  • Treating a negative subtree contribution as mandatory instead of optional — Maximum Path Sum clamps a negative branch to 0 (skip it) rather than being forced to include it just because it exists.
  • Re-searching linearly for a value's position in Construct Binary Tree from Preorder and Inorder Traversal instead of building an index map once up front — turns an O(n) algorithm into O(n²) for no reason.

Takeaways

  • Before coding any tree problem, state what a node needs from its children's answers to compute its own — that's the recursive step; get the base case (usually an empty tree) right and trust the recursion for the rest.
  • Use BFS (a queue) when a problem is organized by depth/level; use the right DFS order (inorder for BST-sorted-order problems, postorder when a node needs both children's answers first) otherwise.
  • A BST's property is a global constraint (bounded by every ancestor), not just a local comparison against the immediate parent — the same global-vs-local distinction that makes its LCA and validation problems trickier than they first look.
  • A BST's ordering isn't just a nice property — it's what turns an O(n) general-tree algorithm into an O(h) one, whenever the problem lets you exploit it.

Try it: the core recursive pattern

Invert Binary Tree is the cleanest possible demonstration of "figure out what a node needs from its children, then trust the recursion."

Live

Loading starter code…

Try it: BFS, level by level

Binary Tree Level Order Traversal is the canonical case for a queue-based traversal instead of recursion — get the level-size timing right here before Right Side View later in this set.

Live

Loading starter code…

Try it: the BST's global constraint

Validate Binary Search Tree is the walkthrough above, in full — the range-threading technique here is worth getting comfortable with before the two Lowest Common Ancestor problems.

Live

Loading starter code…

Keep going

That's 3 of the 12 problems in this topic. Binary Tree Maximum Path Sum is worth doing once these three feel automatic — it's the hardest problem in this topic, and the one most worth taking slowly. See the full Trees set →

Checkpoint · Trees

5 questions · pass at 70%

Q01
What's the actual bug in checking a binary search tree by only comparing each node to its immediate parent?
WhyA node can be greater than its immediate parent (satisfying the local check) while still being greater than some higher ancestor whose subtree it's supposed to stay under -- catching this requires threading a valid (low, high) range down through the whole recursion, not just comparing to the direct parent.
Q02
Why does Lowest Common Ancestor of a BST run in O(h) time while Lowest Common Ancestor of a general Binary Tree needs O(n)?
WhyThe BST's sortedness is what unlocks the shortcut: comparing p and q against the current node's value tells you definitively which one subtree contains the answer, so you never need to explore the other side. A general tree has no such guarantee, so the algorithm must genuinely check both subtrees in the worst case.
Q03
In Binary Tree Maximum Path Sum, why can't the value a node RETURNS to its parent be the same as the best path sum seen so far?
WhyA path handed upward to a parent must still be extendable, which means it can only continue through one branch. The best-answer-so-far has no such restriction -- it's allowed to combine both a node's left and right branches into one path, since it's a final answer, not something that needs to connect further up the tree.
Q04
Why does Construct Binary Tree from Preorder and Inorder Traversal build a hash map from value to inorder-index up front, rather than searching the inorder array directly each time?
WhyWithout the hash map, finding each subtree root's position in the inorder array requires an O(n) scan, and that happens once per node -- O(n) nodes times O(n) search each gives O(n^2) overall. Building the index map once up front makes every lookup O(1), keeping the whole algorithm O(n).
Q05
Binary Tree Level Order Traversal records the queue's size at the START of each iteration, before processing any nodes in that batch. Why is that specific timing necessary?
WhyIf you checked the queue's size partway through processing a level, newly-pushed children from nodes already processed in this same batch would inflate the count, causing the level to include some of the next level's nodes too. Capturing the size before any processing begins is what correctly isolates one level at a time.

Finished Trees?

Pass the quiz to complete it automatically.