Preorder Template
Platform Connection — Parameter Fixing: When you pass
remainingSum = 22from the root and subtract each node's value as you descend, you're doing exactly what our DP course calls parameter fixing. At the root, the problem is unconstrained. At the left child (value 5), the parameter tightens: "your entire subtree must find a path summing to 17." Each level fixes one more dimension of the problem until a leaf either satisfies or violates the constraint.
Information Flows Two Ways
In every problem so far, information flowed up. Leaves computed something, parents combined it. The root got the final answer. That's bottom-up — postorder.
But some questions can't be answered bottom-up. Consider: "Does any root-to-leaf path sum to 22?" A leaf doesn't know what's above it. It sees its own value — say, 2 — and has no idea whether the nodes above sum to 20.
The information the leaf needs lives at the root. It has to be passed down.
Top-down (preorder) pattern: carry accumulated state as a parameter. Process the node BEFORE recursing into children. The state grows as you descend.
This is the opposite direction from postorder. In postorder, you wait for children to report back. In preorder, you tell children what they need to know.
The Template
Every top-down problem follows this shape:
void preorder(TreeNode* node, State accumulated) {
if (!node) return;
State updated = combine(accumulated, node->val);
if (!node->left && !node->right) {
process(updated);
return;
}
preorder(node->left, updated);
preorder(node->right, updated);
}
Three moving parts:
accumulated— what you've gathered so far from root to here.combine— how this node's value extends the accumulated state.process— what to do at a leaf (or along the way).
The leaf check (!left && !right) matters. If you don't check it, you'll process nullptr children as if they were leaves and double-count paths.

Problem 1: Root-to-Leaf Sum (LC 112)
Problem: Given a binary tree and an integer targetSum, return true if there exists a root-to-leaf path where the values sum to targetSum.
Target sum = 22. The path 5 → 4 → 11 → 2 sums to 22. Answer: true.
The accumulated state is the running sum from root to the current node. At each node, you add the node's value. At a leaf, you check if the sum equals the target.
Full Trace
Starting with runningSum = 0 at the root:
| Node | Running Sum Before | After Adding Node | Is Leaf? | Check |
|---|---|---|---|---|
| 5 | 0 | 5 | No | — |
| 4 | 5 | 9 | No | — |
| 11 | 9 | 20 | No | — |
| 7 | 20 | 27 | Yes | \(27 \neq 22\) |
| 2 | 20 | 22 | Yes | \(22 = 22\) — found! |
The recursion short-circuits when it finds a match. We never even visit node 8.
The Code
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) return false;
int remaining = targetSum - root->val;
if (!root->left && !root->right) return remaining == 0;
return hasPathSum(root->left, remaining) || hasPathSum(root->right, remaining);
}
Notice: instead of accumulating the sum upward, we subtract the node's value from the target. When we reach a leaf, we check if the remaining target is 0. Same logic, slightly cleaner — no extra parameter needed because targetSum itself carries the state.
Complexity: \(O(n)\) time, \(O(h)\) space where \(h\) is the tree height (recursion stack).
Problem 2: Root-to-Leaf Paths as Strings (LC 257)
Problem: Return all root-to-leaf paths as strings in the format "1->2->5".
Same tree:
Expected output: ["5->4->11->7", "5->4->11->2", "5->8"]
Here the accumulated state is a string — the path built so far. At each node, append the node's value. At a leaf, save the completed path.
Full Trace
| Node | Path So Far | After Appending | Is Leaf? | Action |
|---|---|---|---|---|
| 5 | "" |
"5" |
No | recurse |
| 4 | "5" |
"5->4" |
No | recurse |
| 11 | "5->4" |
"5->4->11" |
No | recurse |
| 7 | "5->4->11" |
"5->4->11->7" |
Yes | save path |
| 2 | "5->4->11" |
"5->4->11->2" |
Yes | save path |
| 8 | "5" |
"5->8" |
Yes | save path |
Key observation: when we backtrack from 7 to 11, the path is still "5->4->11". The string "5->4->11->7" was a local copy — it doesn't pollute the path for 2. This is why passing state by value (not by reference) gives you automatic backtracking for free.
The Code
void allPaths(TreeNode* node, string pathSoFar, vector<string>& result) {
if (!node) return;
string currentPath = pathSoFar.empty()
? to_string(node->val)
: pathSoFar + "->" + to_string(node->val);
if (!node->left && !node->right) {
result.push_back(currentPath);
return;
}
allPaths(node->left, currentPath, result);
allPaths(node->right, currentPath, result);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
allPaths(root, "", result);
return result;
}
Complexity: \(O(n \cdot h)\) time (each path is up to \(h\) long, and string concatenation costs \(O(h)\)), \(O(n \cdot h)\) space for storing all paths.
Bottom-Up vs Top-Down: When to Use Which
The distinction is simple:
- Bottom-up (postorder): the answer at a node depends on answers from its children. Example: height, diameter, subtree sum.
- Top-down (preorder): the answer at a node depends on information from its ancestors. Example: root-to-leaf sum, depth, path string.
Some problems can be solved either way. Root-to-leaf sum can be done top-down (accumulate sum going down) or bottom-up (subtract from target). But the top-down framing is more natural when the question involves "the path from root to here."
Rule of thumb: if the problem says "root-to-leaf" or "root-to-node," think top-down. If it says "subtree" or "combine children," think bottom-up.
Try It
The starter code has a hasPathSum function with a TODO. Fill in the body.
Predict before running: What does hasPathSum return on a single-node tree with val = 5 and targetSum = 5? Trace it: remaining = 5 - 5 = 0, node is a leaf, return 0 == 0 → true. Correct.
Challenge: What if targetSum = 0 and the tree is a single node with val = 0? What if the tree has val = 0 but also has children? Make sure your solution handles both.
Edge Cases to Watch For
- Node with only one child: If
root->leftis null butroot->rightis not, noderootis NOT a leaf. Do not check for a path sum match atroot. You must guard with!root->left && !root->rightbefore declaring a leaf match, otherwise the path incorrectly terminates at an internal node.
Problems
| Problem | Link | Difficulty |
|---|---|---|
| LC 112 — Path Sum | leetcode.com/problems/path-sum | Easy |
| LC 257 — Binary Tree Paths | leetcode.com/problems/binary-tree-paths | Easy |
| LC 113 — Path Sum II | leetcode.com/problems/path-sum-ii | Medium |
| LC 129 — Sum Root to Leaf Numbers | leetcode.com/problems/sum-root-to-leaf-numbers | Medium |