22

Tree Recursion

Every tree problem asks one question at every node: what do my children know, and what must I tell my parent?

intermediate~110 min8 simulations18 problems

▶ guided lesson⚡ 1-minute summary↻ 15 recall cards

before this: Recursion · Traversals

01 · why it exists

Why this exists, and the weight it carries

since1960

Tree recursion (solve the children, combine)

John McCarthy

In his 1960 Lisp paper, McCarthy built all data from ordered pairs: an S-expression is a pair whose two parts are atoms or smaller S-expressions — a binary tree. He then defined functions on them recursively. His equal says two trees are equal if they are the same atom, or if their first halves are equal and their second halves are equal — the same check as LeetCode's Same Tree. His subst rebuilds a tree from its two rebuilt halves. Every "solve left, solve right, combine" function in this chapter has that shape.

the problem it solvedComputing anything about a nested structure — equality, size, a transformed copy — with a function as short as the structure's definition.

its jobs today

  • Git: a folder's tree object holds the hashes of its files and sub-folders, so hashes are computed bottom-up — a postorder over the project.
  • Disk usage: a folder's size is its files plus its sub-folders' sizes, computed after the children report.
  • Compilers and interpreters: an expression tree is evaluated or simplified bottom-up — b * c before a + b * c.

the weight it carries

Illustrative: a 10⁴-node skewed tree. Asking for every node's height from scratch costs 1 + 2 + … + 10⁴ = 50,005,000 node visits. Returning the height up from one postorder pass costs 10⁴ visits — about 5,000× less.

without it

Every subtree question would be re-asked from scratch at each ancestor — O(n²) instead of O(n) on deep trees.

when · why · how

when
A node's answer depends only on its children's answers: height, size, sum, balanced, equal, mirrored.
why
One postorder pass: O(n) time and O(h) stack. The price is recursion depth on skewed trees.
how
int f(TreeNode node) { if (node == null) return BASE; int l = f(node.left), r = f(node.right); return combine(node.val, l, r); }

02 · plain english

The idea in 30 seconds

Most tree solutions are tiny recursive functions: solve the left subtree, solve the right subtree, combine with this node.

Information flows two ways: down as parameters (depth, bounds, path so far) and up as return values (height, sum, found?).

When the answer is a path that may bend at any node, return one thing to the parent and record another in a global best.

picture this

File Explorer → Properties on a folder: it can't show that projects is 2 GB until every sub-folder has reported its own size — answers flow up. A file's full path flows the other way: /home/priya hands its path down, and projects adds its own name.

03 · remember

What you must know

  • The leap of faith: say what f(node) returns in one sentence, assume f(node.left) and f(node.right) already return exactly that, and only write how to combine them with node. The base case is null.
  • Return info up (postorder): height, size, sum, is-balanced, found-p — anything a node can only know after its children finish.
  • Pass info down (preorder parameters): depth, allowed range, remaining sum, max so far, the number spelled so far — anything a node already knows before visiting its children.
  • Ints copy, lists don't: Java passes an int by value, so remaining or max needs no undo. A shared List or HashMap is one object on the heap — add before the recursive calls, remove after (Path Sum II, III).
  • Root-to-leaf paths end only at a leaf: test node.left == null && node.right == null, never node == null — a node with one child is not a leaf.
  • Any downward path = prefix sums on the current path: the path from just below A down to X adds up to k when prefix(X) − prefix(A) = k, so a HashMap of the prefixes above X counts those paths in O(1).
  • Return one thing, record another: when the answer may bend at any node, return the best one-sided arm to the parent and update a global best with the two-sided bend (Diameter, Max Path Sum).
  • Two facts at once: return a record, an int[], or a sentinel like −1 meaning "already invalid", so the bad news travels up in O(1).
  • Recomputing is the classic slowdown: calling height() inside a recursion that already visits every node is O(n²) on a skewed tree. Return the height from the same pass → O(n).
  • Two trees in lockstep: recurse on (a.left, b.left) and (a.right, b.right) together — Same Tree; cross the pairs for a mirror (Symmetric Tree). Many subtree comparisons? Give each subtree shape an id so equal subtrees compare in O(1).
  • Building a tree is recursion too: preorder (from the front) or postorder (from the back) tells you the next root; inorder tells you which values go left and right of it.
  • Space is O(h): O(log n) when balanced, O(n) when skewed. Java has no tail-call optimization, so for 10⁵-deep chains switch to an explicit stack or parent pointers.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Max depth — answers flow up

Calls go down to the leaves; each node's answer (↑) appears only after both children have reported.

loading simulation…
sim

Path sum — the target flows down

Each call subtracts its value and hands the rest (r) to its children; only a leaf may decide that a path is complete.

loading simulation…
sim

Invert — trust the calls, then swap

Each call mirrors both subtrees first and then swaps them, so the tree flips from the bottom up.

loading simulation…
sim

Symmetric — two halves in lockstep

a and b walk the two halves together: outside pair first, then inside pair. One mismatch stops everything.

loading simulation…
sim

Diameter — return height, record the best bend

`sub` is the height a node returns to its parent; the glowing path is the best bend recorded so far.

loading simulation…
sim

Max path sum — drop negative arms

Same shape as Diameter, with values: an arm worth less than 0 is replaced by 0, and the bend uses both arms.

loading simulation…
sim

Lowest common ancestor

Every call reports what it found. The first node that hears back from *both* sides is the answer.

loading simulation…
sim

Path sum III — prefix sums on the current path

s is the sum from the root. The HashMap holds the prefixes above the current node; each one is taken back out on the way up.

loading simulation…

05 · cost

Time & space, explained

Tree recursions (n nodes, height h)
TimeExtra spacewhy
Height, size, sum, balanced — one postorder passO(n)O(h)Each node is visited once with O(1) work; the stack holds one root-to-node path.
Diameter / max path sum with a global bestO(n)O(h)Still one pass: the bend is recorded at the node, only one arm travels up.
Same pattern, but height() recomputed at every nodeO(n²) worstO(h)Each node's height is recomputed once per ancestor: n + (n − 1) + … on a skewed tree (O(n log n) when balanced).
Same tree / mirror (two trees in lockstep)O(min(n, m))O(min(h₁, h₂))Stops at the first difference; never goes deeper than the shorter tree.
Subtree: Same Tree started at every node (subRoot has m nodes)O(n·m) worstO(h₁ + h₂)Up to m comparisons at each of n starting points — long chains of equal values are the bad case.
Subtree: one id per subtree shape (HashMap)O(n + m) expectedO(n + m)Each node builds one short key and does one O(1)-average lookup; the map stores each distinct shape once.
Path state passed down (Path Sum, Good Nodes, Sum Root to Leaf)O(n)O(h)One visit per node; the state is one int per stack frame.
Path Sum II: one shared path, k matching paths copiedO(n + k·h)O(h) + outputO(1) add/remove per node; only the k saved paths are copied, O(h) each.
Path Sum III: start a walk at every nodeO(n·h)O(h)Each node is walked once from every ancestor: n log n balanced, n²/2 for a chain.
Path Sum III: prefix sums + HashMap with undoO(n) averageO(n)One O(1)-average lookup and update per node; the map keeps at most one key per node.
Count nodes of a complete tree (perfect-subtree shortcut)O(log² n)O(log n)About log n levels do real work, each with an O(log n) walk down the outer edges.
Flatten to a list in place (splice under the left subtree)O(n)O(1)Each node is searched as a rightmost node at most once; no recursion, no list.
Lowest common ancestor, one recursive passO(n)O(h)Each node reports at most once.
Build from preorder (or postorder) + inorder with a HashMapO(n)O(n)Each node is created once with an O(1) index lookup; the map holds n entries.
Same build with a linear search for each rootO(n²) worstO(h)Each of n roots may scan the whole inorder range on a skewed tree.
Why recomputing hurts — n = 10⁴ nodes
Balanced (h ≈ 13)Skewed chain (h = n − 1)why
Height returned up in one pass10⁴ visits10⁴ visitsShape doesn't matter: every node is visited exactly once.
Height recomputed at every node≈ n·log₂ n ≈ 1.3·10⁵n(n + 1)/2 ≈ 5·10⁷Each node is re-walked once per ancestor: about 13 ancestors when balanced, up to n when skewed.
Recursion depth≈ 14 frames10⁴ frames — risky on a small stackDepth = height + 1; a chain makes recursion as deep as the tree is big.

06 · java

Raw vs Collections Framework

Pass info down vs return info up

javapass down (depth)java.util.*
class PassDown {    /** Depth flows DOWN as a parameter: a child's depth = parent's depth + 1. */    static void depths(TreeNode node, int depth, Map<Integer, Integer> out) {        if (node == null) return;        out.put(node.val, depth);                 // known BEFORE visiting the children        depths(node.left, depth + 1, out);        depths(node.right, depth + 1, out);    }}
  • Depth is known before visiting a node (parent's depth + 1), so it travels down as a parameter.
  • Height, size and sum are known only after the children finish, so they travel up as return values — a postorder.
  • A Java record (Java 16+) returns several facts at once. In interviews an int[] or a field is also fine; say why you chose it.

Path state: a fresh copy per call vs one shared list

javanew ArrayList in every calljava.util.*
class Solution {    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {        List<List<Integer>> result = new ArrayList<>();        walk(root, targetSum, new ArrayList<>(), result);        return result;    }     /** pathAbove is never changed: every call makes its own, longer copy. */    private void walk(TreeNode node, int remaining, List<Integer> pathAbove, List<List<Integer>> result) {        if (node == null) return;        List<Integer> path = new ArrayList<>(pathAbove);     // O(h) copy at EVERY node        path.add(node.val);        remaining -= node.val;        if (node.left == null && node.right == null && remaining == 0) result.add(path);        walk(node.left, remaining, path, result);        walk(node.right, remaining, path, result);    }}
  • Java passes an int by value: each call gets its own remaining, so a child can't spoil the parent's copy and there is nothing to undo.
  • A List is one object on the heap; every call holds a reference to the same one. Whatever a call adds, it removes before returning.
  • Save new ArrayList<>(path), not path — otherwise every saved answer is the same list, and it ends up empty.
  • path.remove(path.size() - 1) removes by index. On a List<Integer>, path.remove(node.val) also picks the index overload — a classic bug.

Returning two facts: a sentinel vs a record

java−1 sentineljava.util.*
class Solution {    private static final int UNBALANCED = -1;     public boolean isBalanced(TreeNode root) {        return check(root) != UNBALANCED;    }     /** Height of the subtree, or -1 as soon as any part of it is unbalanced. */    private int check(TreeNode node) {        if (node == null) return 0;        int left = check(node.left);        if (left == UNBALANCED) return UNBALANCED;          // stop early, pass the bad news up        int right = check(node.right);        if (right == UNBALANCED) return UNBALANCED;        if (Math.abs(left - right) > 1) return UNBALANCED;        return 1 + Math.max(left, right);    }}
  • The sentinel packs "unbalanced" into an impossible height (−1). Short, fast, and the usual interview answer — name the constant so it isn't magic.
  • The record says what it means: Status(boolean balanced, int height). A little more code, much easier to read six months later.
  • Both stop early: once a subtree is bad, the bad news goes straight up without checking the other side.

A field for the global best

javafield best, reset per calljava.util.*
class Solution {    private int best;     public int diameterOfBinaryTree(TreeNode root) {        best = 0;        height(root);        return best;    }     /** Returns the height (in nodes) and updates the best path seen so far. */    private int height(TreeNode node) {        if (node == null) return 0;        int left = height(node.left);        int right = height(node.right);        best = Math.max(best, left + right);           // path that bends here, in edges        return 1 + Math.max(left, right);              // what my parent needs    }}
  • The helper returns what the parent needs (height); the field records the answer (best bend). Mixing them up is the classic bug.
  • Reset the field at the start of the public method, so calling it twice on one object still works.
  • Prefer no fields? Pass an int[] best = {0} into the helper, or return a record of (height, best).

Deep trees: recursion vs an explicit stack

javarecursivejava.util.*
class Solution {    public int maxDepth(TreeNode root) {        if (root == null) return 0;                // empty tree has depth 0        int left = maxDepth(root.left);            // ask the left subtree        int right = maxDepth(root.right);          // ask the right subtree        return 1 + Math.max(left, right);          // me + the deeper side    }}
  • Recursion depth = tree height. A 10⁵-node chain can overflow a default Java thread stack; ArrayDeque lives on the heap.
  • To make a postorder iterative you need to know when both children are done — often easier to switch to BFS or to carry state (like depth) with each node.
  • Java never turns recursion into a loop for you (no tail-call optimization).
Java API you will actually use
calldoescost
record Info(int size, int height) {}Return several values from one recursive callO(1)
Math.max(l, r) / Math.abs(l - r)Combine the children's answersO(1)
Integer.MIN_VALUEStarting value for a max when values can be negative—
int[] best = {Integer.MIN_VALUE}A mutable holder instead of a fieldO(1)
Map<Integer, Integer> indexOf = new HashMap<>()Value → inorder index when rebuilding a treeO(1) average
Map<TreeNode, TreeNode> parentParent pointers for an iterative LCAO(1) average
a == b on TreeNodeCompare nodes by reference (LCA's p and q)O(1)
Deque<TreeNode> stack = new ArrayDeque<>()Explicit stack when the tree may be very deepO(1) amortized
path.remove(path.size() - 1)Undo the last add on the way back (by index)O(1)
result.add(new ArrayList<>(path))Save a snapshot of the current pathO(h)
seen.merge(sum, 1, Integer::sum)Count a prefix sum; merge with −1 to take it backO(1) average
ids.computeIfAbsent(key, k -> ids.size() + 1)Give a new subtree shape the next id, or return the old oneO(1) average
(1 << h) - 1Nodes in a perfect tree with h levelsO(1)

A node's answer depends on its children (height, balanced, sum)

→ Postorder: return info up

Children finish before the parent combines their results.

A node's answer depends on its ancestors (depth, bounds, path so far)

→ Preorder: pass info down as parameters

The parent knows it before the child runs.

The best path may bend at any node

→ Return one arm, update a global best

A parent can extend only one side; the bend is complete at this node.

Two facts from one subtree

→ A record, an int[], or a sentinel

One pass instead of two.

Compare or mirror two trees

→ Recursion on pairs of nodes

Both trees are walked together, stopping at the first difference.

Record every root-to-leaf path that works

→ One shared List: add, recurse, remove; copy at a match

O(1) per step instead of copying the path at every node.

Count downward paths with sum k (any start, any end)

→ Prefix sums of the current path in a HashMap<Long, Integer>, undone on return

O(n) instead of starting a fresh walk at every node.

Many "is this subtree equal to that one?" checks

→ An id per subtree shape: (left id, value, right id) in a HashMap

Each check becomes one int comparison.

Rebuild a tree from preorder (or postorder) + inorder

→ Running index into preorder (or backwards through postorder) + HashMap of inorder positions

O(1) to find where each root splits its range.

Depth may reach 10⁵

→ Explicit ArrayDeque stack, BFS, or a parent map

Heap memory instead of the thread stack.

07 · recognise

Spot the pattern

"Maximum/minimum depth", "height", "is balanced"

Postorder: return the height up; use −1 or a record for "invalid"

"Diameter", "longest path", "any path" (doesn't have to pass the root)

Return the best one-sided arm; record the two-sided bend in a global best

"Path sum" from root to leaf, "depth of every node", "max so far on the path"

Pass the running value down as a parameter; decide at a leaf (both children null), not at null

"Return all the paths"

Backtracking: one shared list — add, recurse, remove — and save copies

"Number of paths" that may start and end anywhere, going down

Prefix sums on the current path + HashMap seeded with {0: 1}; undo when leaving a node

"Complete tree", "count the nodes in less than O(n)"

Compare the leftmost and rightmost depths; equal → perfect → 2^h − 1

"Flatten" a tree into a list "in place"

Preorder rewiring: hang the right subtree under the left subtree's rightmost node

"Ancestor", "where do p and q meet"

Recursive LCA: return what you found; the split point is the answer

"Same", "mirror", "symmetric", "subtree of" another tree

Walk both trees in lockstep with one recursion

"Construct / rebuild" from traversals

Preorder from the front (or postorder from the back) gives the roots; inorder (via a HashMap) gives the left/right split

Deep tree (n up to 10⁵) and Java recursion

Mention StackOverflow risk; offer an iterative version

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Mixing up depth conventions: LeetCode's maximum depth counts nodes (one node → 1), textbook height counts edges (one node → 0). Say which one you use.
  • Forgetting the null base case — the first leaf crashes with a NullPointerException.
  • Returning the global answer instead of what the parent needs (e.g. returning the diameter instead of the height) — the recursion then combines the wrong things.
  • Starting a max at 0 when values can be negative (Max Path Sum on [-3] must return −3). Start at Integer.MIN_VALUE.
  • Comparing node values when the problem gives node references (LCA): compare with == on references.
  • Calling height() inside a recursion that already visits every node — O(n²) on a skewed tree.
  • Invert: root.left = invertTree(root.right); root.right = invertTree(root.left); — the second line reads the left side you just overwrote. Save one side first.
  • Construct from preorder + inorder: building the right subtree before the left with a running preorder index — the tokens no longer line up.
  • A field used as the global best but not reset in the public method: the second call starts from the first call's answer.
  • Checking a root-to-leaf condition at null instead of at a leaf: a node with one child is not a leaf (Path Sum on [1,2] with target 1 must be false).
  • Saving path itself instead of new ArrayList<>(path), or undoing with path.remove(node.val) — on a List<Integer> that removes by index.
  • Path Sum III with int sums (values reach 10⁹), or without undoing the prefix count on the way back — sibling branches then share prefixes.

tricks

  • Practise in families (the problems below follow this order): return-up summaries (Depth, Balanced) → compare or mirror (Same, Invert, Symmetric, Subtree) → path state down (Path Sum I and II, Sum Root to Leaf) → counting (Good Nodes, Complete Tree, Path Sum III) → global best (Diameter, Max Path Sum) → LCA → build or reshape (two Constructs, Flatten).
  • Before coding, say the function's contract in one sentence: "height(node) returns the number of nodes on the longest downward path." Then the code writes itself.
  • Chalo, dry-run karte hain — on a 5–7 node tree with one leaf, one node with only a left child, and the root. That covers almost every bug.
  • Edge cases to test every time: empty tree, single node, completely left-skewed, completely right-skewed, negative values.
  • Need two facts from a subtree (height and balanced)? Return both — a record, an int[], or a sentinel like −1.
  • State space as O(h), then explain: O(log n) if balanced, O(n) if skewed. Interviewers listen for this.
  • When the answer can bend, write two comments before the code: // returns: … and // records: …. They are never the same thing.

09 · why & how

Questions you might be too shy to ask

How do I "think recursively" about trees without getting lost?

Don't trace the whole recursion in your head. Trust that the call on a child returns the right answer for that child's subtree (the leap of faith). Then only answer two questions: what is the answer for null, and how do I combine my two children's answers with my own value?

What exactly is f(node.left) giving me?

The answer for the subtree that starts at node.left — not for the whole tree. height(node.left) is the height of just the left part, as if node.left were the root of its own small tree. Only at the top does it become the real answer: height(root) is the whole tree's height. Write that meaning as a comment above the method (// returns: height of the subtree at node) and every line gets easier.

What should my function return for null?

Whatever makes your combine line correct without special cases. Height counted in nodes → 0. Sum or count → 0. "Are these equal / balanced?" → true. LCA → null ("found nothing here"). Max Path Sum's arm → 0 (an empty arm adds nothing). Check it on a single node: 1 + max(0, 0) = 1. Sahi aaya? Then your base case is right.

Two recursive calls per node — isn't that exponential like Fibonacci?

No. Fibonacci's two calls overlap: fib(5) and fib(4) both recompute fib(3), again and again. In a tree, the two calls go to two different subtrees, which share no nodes. So every node gets exactly one call (plus one quick call per empty null child): about 2n calls → O(n) total, as long as each call does O(1) work.

I return true deep inside the recursion — why does the root still return false?

A return only goes back to the one call that called it, not to the top. That parent must pass the news along: return hasPathSum(root.left, remaining) || hasPathSum(root.right, remaining);. If the parent just calls hasPathSum(root.left, remaining); and ignores the result, the true is lost. Rule: whenever a call returns something you need, use its value.

How do I decide whether data flows down or up?

Ask: can this node know it before looking at its children? Depth, the path so far, an allowed range — yes, so pass them down as parameters. Height, size, "did I find p?" — no, the children must answer first, so return them up.

When do I need a separate helper method?

When the method LeetCode gives you can't carry what you need. Need an extra parameter (depth, remaining sum, max so far)? Write dfs(node, depth) and call it from the given method. Need to return something different from the final answer? Diameter's helper returns a height, while diameterOfBinaryTree returns the best bend. If neither is true (Max Depth, Invert, Same Tree), just recurse on the given method itself.

Is a global variable (field) in a recursive solution bad style?

It's normal and accepted in interviews for "best so far" answers (Diameter, Max Path Sum). Reset it at the start of the public method so repeated calls work. If you prefer, pass an int[] best or return a record instead.

Why does remaining need no undo, but path does?

Java passes primitives by value: each call gets its own copy of remaining, so a child's change can't leak back to the parent. A List is an object — every call holds a reference to the same one on the heap. So whatever a call adds, it must remove before returning, or the parent and its other children see a wrong path.

Path Sum III: why put 0 in the map before starting?

The map answers "how many prefixes above me equal sum − target?". A path that starts at the root has nothing above it — the prefix before it is the empty sum, 0. Seeding {0: 1} counts those paths too. Subarray Sum Equals K does exactly the same on an array.

Why does Diameter return the height but record left + right?

Because the parent and the answer want different things. The parent can only extend the path through one child, so it needs the longest single arm (the height). The path that bends at this node uses both arms — that's a candidate answer, so it goes into best, not up.

Why can LCA return as soon as it sees p, without looking below p?

Because both nodes are guaranteed to be in the tree. If q is below p, then p is the answer anyway; if q is elsewhere, another branch will report q and the split point is higher up. (If p or q might be missing — LC 1644 — you must search the whole tree.)

Which pairs of traversals can rebuild a tree?

Preorder + inorder, or postorder + inorder, with unique values. Preorder + postorder cannot in general: if a node has a single child, nothing tells you whether it's the left or the right one.

How deep can my recursion go in Java before it breaks?

It depends on the thread's stack size and each frame's size — typically thousands to tens of thousands of calls. Balanced trees are fine (a million nodes is only ~20 deep). Skewed trees with 10⁵ nodes can throw StackOverflowError; say so and offer an iterative version.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #104
    Maximum Depth of Binary Treemust-do
    EasyLinkedInMicrosoftAppleBloombergUber+1
  2. #110
    Balanced Binary Tree
    EasyBloombergAmazonMicrosoftMetaGoogle
  3. #100
    Same Tree
    EasyBloombergAmazonGoogleMicrosoftMeta+1
  4. #226
    Invert Binary Treemust-do
    EasyGoogleAmazonBloombergMicrosoftMeta+1
  5. #101
    Symmetric Treemust-do
    EasyLinkedInBloombergMicrosoftAmazonGoogle+1
  6. #572
    Subtree of Another Treemust-do
    EasyMetaAmazonGoogleMicrosoft
  7. #112
    Path Sum
    EasyMicrosoftAmazonBloombergMeta
  8. #113
    Path Sum IImust-do
    MediumBloombergMicrosoftMetaAmazon
  9. #129
    Sum Root to Leaf Numbers
    MediumMetaGoogleAmazonMicrosoft
  10. #1448
    Count Good Nodes in Binary Treemust-do
    MediumMicrosoftAmazonGoogle
  11. #222
    Count Complete Tree Nodes
    MediumGoogleMicrosoftBloombergAmazon
  12. #437
    Path Sum IIImust-do
    MediumAmazonGoogleMicrosoftMeta
  13. #543
    Diameter of Binary Treemust-do
    EasyMetaAmazonOracleBloombergGoogle+1
  14. #124
    Binary Tree Maximum Path Summust-do
    HardAdobeAmazonByteDance / TikTokSalesforceMeta+1
  15. #236
    Lowest Common Ancestor of a Binary Treemust-do
    MediumMetaAtlassianAmazonAdobeLinkedIn+1
  16. #105
    Construct Binary Tree from Preorder and Inorder Traversal
    MediumByteDance / TikTokAdobeBloombergAmazonMicrosoft+1
  17. #106
    Construct Binary Tree from Inorder and Postorder Traversal
    MediumWalmartMicrosoftAmazonGoogleBloomberg
  18. #114
    Flatten Binary Tree to Linked List
    MediumMicrosoftAmazonMetaGoogle

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #98
    Validate Binary Search Tree
    Mediumin BST

    Pass info down: each node receives the (min, max) range its ancestors allow.

  2. #427
    Construct Quad Tree
    Mediumin Recursion

    Trust four recursive calls instead of two, then decide at the parent whether the children merge.

11 · revise

The whole chapter on one screen

10 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    Tree recursion: f(node) = combine(f(left), f(right), node). Trust the child calls; write only the base case and the combine step.

  2. use it when

    Children's answers needed (height, balanced, count) → return up. Ancestors' context needed (depth, remaining sum, max so far) → pass down.

  3. template

    int f(TreeNode n) { if (n == null) return 0; int l = f(n.left), r = f(n.right); best = Math.max(best, l + r); return 1 + Math.max(l, r); }

  4. cost

    One pass: O(n) time, O(h) stack — about 20 frames for a balanced 10⁶-node tree, 10⁶ for a chain. Recomputing height per node: O(n²).

  5. trap

    Returning the answer instead of what the parent needs: Diameter returns the height and records left + right — never returns the bend.

  6. trap

    Paths end only at a leaf (left == null && right == null), never at null. Save new ArrayList<>(path), then path.remove(path.size() - 1).

  7. trap

    best = 0 with negative values: Max Path Sum on [-3] must give −3. Start at Integer.MIN_VALUE, reset it in the public method.

  8. pro tip

    Any downward path sums to k: prefix sums of the current path in a HashMap seeded {0: 1}; look up sum − k, add sum, recurse, take it back out.

  9. pro tip

    LCA: return root if it's null, p or q; if both sides report something, root is the split point.

  10. pro tip

    Rebuild: next preorder value (or postorder from the back) = root; a HashMap of inorder positions splits left/right; build left first (right first for postorder).

12 · recall

Flip, remember, repeat

card 1 / 15
recall

What is the "leap of faith" when writing a tree recursion?

tap or Space to flip ↻

Question 1: What is the "leap of faith" when writing a tree recursion?

13 · check yourself

Quick quiz

  1. Which traversal must compute a node's height?

  2. In Diameter of Binary Tree, what should `height(node)` return to its parent?

  3. Max Path Sum: what should `best` start at?

  4. Which of these is naturally passed DOWN as a parameter?

  5. Checking balance by calling `height()` at every node costs what on a skewed tree of n nodes?

  6. Recursive LCA returns as soon as `root == p`. Why is that safe here?

  7. Construct from preorder + inorder with a running preorder index: why build the left subtree first?

  8. A tree has 7 nodes. What's the smallest possible height (in edges)?

  9. Symmetric Tree: after checking a.val == b.val, which calls come next?

  10. Path Sum with `if (root == null) return targetSum == 0;` — what does it return for [1,2] with target 1?

  11. Path Sum II saves matches with `result.add(path)` (no copy). With two matching paths, what comes back?

  12. Path Sum III: why seed the HashMap with {0: 1}?

  13. Count Complete Tree Nodes: the leftmost and rightmost paths both have 4 nodes. How many nodes does the tree have?