1. Problem and Motivation
Tree DP is dynamic programming where the recurrence follows the tree's parent-child structure instead of an array's linear index order. In array DP, $dp[i]$ is computed from $dp[i-1]$, $dp[i-2]$, and so on — a fixed, linear order of dependencies. In tree DP, a node's answer depends on its children's answers, and a node can have zero, one, two, or many children. The natural computation order is therefore a postorder traversal: visit every child subtree completely, combine what they return, and only then compute the current node's own answer.
This ordering is not optional. A parent's DP value is usually undefined until every child's DP value is known, exactly as $dp[i]$ in array DP is undefined until $dp[i-1]$ is known. The difference is that "the previous state" is no longer a single neighbor at index $i-1$ — it is a set of children, and the recurrence must specify how to combine an arbitrary number of child results into one parent result.
2. Prerequisites
This lesson builds directly on two earlier parts of the series and does not re-derive either one:
- Tree traversal mechanics — postorder, preorder, and recursive tree walks are covered in Part 9: Trees, Tree Traversals. If postorder recursion (left, right, then process the node) is unfamiliar, read that section first.
- The include/exclude DP idea — the array-based House Robber recurrence, $dp[i] = \max(dp[i-1],\, dp[i-2] + nums[i])$, is fully covered in Part 15: Core DP Patterns, section 4, House Robber. This lesson generalizes that same include/exclude idea from a linear array to a tree; it does not re-derive it from scratch.
Also assumed: recursion, basic tree/graph adjacency representations, and arithmetic with array indices.
3. A Concrete Example
Binary-Tree House Robber, hand-traced
Tree (LeetCode 337, Example 1): root value 3, whose left child is 2 and right child is another 3. Node 2 has only a right child valued 3. The root's right child (3) has only a right child valued 1.
3
/ \
2 3
\ \
3 1
Postorder visits the leaves first. Each node returns a pair (with, without): the best loot from its subtree if the node itself IS robbed, and the best loot if it is NOT.
| Node | Children processed | with = val + left.without + right.without | without = max(left pair) + max(right pair) |
|---|---|---|---|
| leaf 3 (under node 2) | none | 3 + 0 + 0 = 3 | 0 + 0 = 0 |
| leaf 1 (under root's right 3) | none | 1 + 0 + 0 = 1 | 0 + 0 = 0 |
| node 2 | no left child; right = leaf 3 → (3, 0) | 2 + 0 + 0 = 2 | max(0,0) + max(3,0) = 0 + 3 = 3 |
| node 3 (root's right child) | no left child; right = leaf 1 → (1, 0) | 3 + 0 + 0 = 3 | max(0,0) + max(1,0) = 0 + 1 = 1 |
| root (val 3) | left = node 2 → (2, 3); right = node 3 → (3, 1) | 3 + 3 + 1 = 7 | max(2,3) + max(3,1) = 3 + 3 = 6 |
The root's pair is (7, 6), so the answer is max(7, 6) = 7 — rob the root, the leaf under node 2, and the leaf under the root's right child. This is exactly the published answer for this input.
Notice the shape of the computation: every pair bubbles up from leaves to root, and the parent's pair is a pure function of its children's pairs. That is the entire postorder tree-DP pattern.
4. Postorder State Aggregation and Include/Exclude States
For a node $u$ with children $c_1, \ldots, c_k$:
$$with(u) = val(u) + \sum_{i} without(c_i)$$
$$without(u) = \sum_{i} \max(with(c_i),\, without(c_i))$$
A missing child contributes the pair $(0, 0)$, which keeps the formula uniform for nodes with zero, one, or two children. The final answer is $\max(with(root), without(root))$.
The implementation below solves Binary-Tree House Robber (LeetCode 337) for the general case of a binary tree — the same pair-return technique extends unchanged to a node with any number of children by summing $without(c_i)$ and $\max(with(c_i), without(c_i))$ over all children instead of just two.
5. Tree Knapsack (Choosing k Nodes)
Consider: select a subset of nodes, of size at most $k$, to maximize total value, where a node may be selected only if its parent is also selected (the root has no such restriction). Let $dp[u]$ be an array indexed by budget $j$ from $0$ to $size(u)$, the size of $u$'s subtree. Build it by merging children one at a time: start with a one-element array $[0]$ (nothing selected yet), and for each child $c$, combine the current partial array with $dp[c]$ using $new[j+t] = \max(new[j+t],\, partial[j] + dp[c][t])$. After all children are merged, prefix-max the result so it means "at most" $j$, then set $dp[u][j] = val(u) + merged[j-1]$ for $j \ge 1$ (selecting $u$ costs one unit and unlocks its children), and $dp[u][0] = 0$.
Why the merge is O(n) total, not O(n × budget) per node
At first glance, merging a child of subtree size $s_c$ into a running array of size $s_u$ costs $O(s_u \cdot s_c)$, and summing that over every node looks like it could be quadratic in the budget. The classic result is better: if every merge is capped at the size of the two subtrees actually being combined (as above, not at some global budget $k$), the total merge work across the whole tree is $O(n^2)$ in the worst case — not $O(n \times k)$ per node repeated $n$ times. This falls out of the small-to-large (also called "size doubling") argument: each pair of nodes $(x, y)$ can only be "merged together" once, at the moment their two subtrees first combine into a common ancestor's subtree, and that single merge costs at most $O(size(x) \cdot size(y))$. Summing $size(x)\cdot size(y)$ over all such first-merge pairs across the tree telescopes to $O(n^2)$ total in the worst case (a "broom" or balanced binary tree), which is the same bound naive analysis gives — the value of the small-to-large argument is that it also proves tighter bounds (e.g. $O(n \sqrt n)$ or $O(n\log n)$) hold for many other subtree-merge DPs where the accounting is less trivial. The practical takeaway: always cap each merge at the true combined subtree size, never at the global budget $k$, or the naive-looking bound becomes a real one.
6. BST-Shape DP
Counting structurally distinct binary search trees is a shorter but instructive tree-DP shape: it builds trees rather than consuming a given one. Let $dp[n]$ be the number of structurally distinct BSTs holding $n$ distinct keys. Choosing which key becomes the root at position $i$ (for $i = 1 \ldots n$) splits the remaining keys into a left subtree of size $i-1$ and a right subtree of size $n-i$; the two subtrees vary independently, so their shape counts multiply:
$$dp[n] = \sum_{i=1}^{n} dp[i-1] \cdot dp[n-i], \qquad dp[0] = 1$$
This is precisely the Catalan number recurrence — $dp[n]$ is the $n$-th Catalan number, $C_n$. This lesson only needs the recurrence and the connection, not a derivation of the closed form $C_n = \frac{1}{n+1}\binom{2n}{n}$.
7. Rerooting Technique
Concrete problem: for each node $u$ in an unrooted tree of $n$ nodes, compute the sum of distances from $u$ to every other node. Root the tree arbitrarily at node 0.
Pass 1 (postorder): for each node $u$, compute $count[u]$ = size of $u$'s subtree, and $down[u]$ = sum of distances from $u$ to every node inside its own subtree, using $down[u] = \sum_{c} \big(down[c] + count[c]\big)$ over children $c$ (each node in $c$'s subtree is one edge farther from $u$ than it is from $c$, hence the $+count[c]$ term). $ans[0] = down[0]$, since node 0's subtree is the whole tree.
Pass 2 (preorder): re-derive $ans[c]$ for each child $c$ of a node $u$ whose answer is already known. Moving the root from $u$ to $c$ changes the distance to every other node by exactly $\pm 1$: the $count[c]$ nodes inside $c$'s subtree all get one edge closer (they were measured through $u$, one hop farther than necessary), and the remaining $n - count[c]$ nodes outside $c$'s subtree all get one edge farther (reaching them from $c$ now requires the extra hop back through $u$). So:
$$ans[c] = ans[u] - count[c] + (n - count[c])$$
This is the step that is easy to get subtly wrong: it is not $ans[u] - down[c]$ or a recomputation from scratch — it reuses $ans[u]$ (the parent's already-finished answer over the entire tree) and adjusts it by exactly the $count[c]$ nodes that changed side, never touching $down[c]$ again. A worked check on a 6-node tree (root 0 with children 1 and 2; node 2 has children 3, 4, 5) gives $ans = [8, 12, 6, 10, 10, 10]$, matching a brute-force BFS-from-every-node computation exactly.
8. Complexity Analysis
Binary-Tree House Robber: $O(n)$ time, $O(h)$ recursion-stack space — a single postorder visit per node, each doing $O(1)$ work.
BST-Shape DP: $O(n)$ states, each costing $O(n)$ to compute via the sum over root positions, giving $O(n^2)$ total for a bottom-up table (or a memoized recursion visiting the same $O(n)$ distinct subproblems).
Tree Knapsack: $O(n^2)$ worst case for the merge, via the small-to-large subtree-size argument described above — not the naive-looking $O(n \times budget)$ per node, which would overcount work already bounded by actual subtree sizes. Many tree-knapsack variants tighten this further to $O(n \log n)$ with more careful accounting, but $O(n^2)$ is the safe, provable bound for the merge shown here.
Rerooting: $O(n)$ total — one postorder pass plus one preorder pass, each visiting every node once and doing $O(1)$ (or $O(\deg(u))$) work per node, versus $O(n^2)$ for recomputing the full DP from scratch at every candidate root.
9. Applications and Alternatives
Where tree DP and rerooting show up in practice
- Network diameter and eccentricity: rerooting computes, for every router or node in a network topology, its eccentricity (maximum distance to any other node) in $O(n)$ instead of running BFS from every node.
- Organizational hierarchy optimization: selecting a budget-constrained subset of roles or teams to fund, where funding a role requires its manager's role to also be funded, is a direct instance of the tree knapsack "parent must be selected" constraint.
- Phylogenetic tree analysis: computing the sum (or centroid) of evolutionary distances from every species node to all others reuses the same rerooting recombination as the sum-of-distances example, since phylogenetic trees are also unrooted by nature.
When the tree is small or the answer is only needed for one fixed root, a single postorder DP pass is simpler and sufficient — reach for rerooting specifically when the same tree-DP quantity is required for every node as root, since a full re-run per root turns an $O(n)$ algorithm into an $O(n^2)$ one.
10. Exercises and Mastery
Easy
- Trace the Binary-Tree House Robber pair algorithm on a new 5-node tree of your own construction (a root with two children, each with one grandchild), and verify the pair returned by the root by hand.
- State, in your own words, why a missing child contributes the pair $(0, 0)$ rather than being skipped in the summation.
Medium
- Run the BST-Shape DP for $n=5$ and verify the result equals the known Catalan number $C_5 = 42$. Show the five terms of the sum $dp[5] = \sum_{i=1}^{5} dp[i-1]\cdot dp[5-i]$ that add up to 42.
- Modify the tree knapsack example so a node's "cost" is its own value (not a flat 1 per node), and describe which part of the merge recurrence must change.
Hard
- Use rerooting to find a tree's diameter: for each node, compute the maximum distance to any other node (rather than the sum), then take the overall maximum across all nodes and identify the two endpoint nodes that realize it. Verify your rerooting recombination step for maximum distance differs from the sum-of-distances recombination — it is not a simple $\pm 1$ shift, since the maximum, unlike the sum, is not decomposable by "how many nodes changed side."
Review rhythm: tomorrow, re-derive the include/exclude pair recurrence from memory and re-trace the worked example; next week, implement the tree knapsack merge and the rerooting recombination step in a language you have not used here; several weeks later, find a new problem — network eccentricity, subtree counting, or a fresh LeetCode tree problem — and identify which of these four patterns it matches before looking up a solution.
11. Summary Cheat Sheet
- Tree DP follows the tree's parent-child structure via postorder traversal: children's answers are computed before the parent's.
- Include/exclude on a tree generalizes the array House Robber recurrence: each node returns a (with, without) pair, combined from its children's pairs.
- Tree knapsack merges children's budgeted dp arrays one at a time; capping each merge at the true combined subtree size (small-to-large) keeps total work bounded, unlike naively multiplying by a global budget.
- BST-Shape DP counts structurally distinct trees by summing over root-position choices; the recurrence is the Catalan number recurrence.
- Rerooting computes a tree-DP answer for every node as root using one postorder pass (subtree answers) plus one preorder pass (parent-to-child recombination), turning $O(n^2)$ into $O(n)$.
- The rerooting recombination step reuses the parent's finished answer and adjusts only for the nodes that changed side of the moved edge — it never recomputes from scratch.