Path Sum
Given the root of a binary tree and an integer targetSum, return true if there exists a root-to-leaf path whose node values add up to exactly targetSum, and false otherwise. The path must start at the root and end at a leaf (a node with no children); a partial path that happens to reach the target partway down does not count. An empty tree contains no path at all, so it returns false for every target, including 0.
- The number of nodes in the tree is in the range [0, 5000]
- -1000 <= Node.val <= 1000
- -1000 <= targetSum <= 1000
- The path must end at a leaf, not at an internal node
root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22trueroot = [1,2,3], targetSum = 1falseroot = [1,-2,3], targetSum = -1trueroot = [], targetSum = 0falseWe want to know: does some path from the root down to a leaf add up to exactly the target?
5
/ \
4 8 target = 22
/
11
/ \
7 2
5 + 4 + 11 + 7 = 27 (misses)
5 + 4 + 11 + 2 = 22 at leaf 2 -> trueRead the picture: follow each route from the root down to a leaf and add the values on it. The left-most route (5, 4, 11, 7) totals 27, but the route (5, 4, 11, 2) lands exactly on 22 — so the answer is true. We only need one winning path.
Think about a leaf. Can it tell whether the path ending on it hits the target? No — it has no idea what was already added above it on the way down. That running total lives in its ancestors, not below it. So there's nothing useful to ask the children for; instead, I have to carry the number down from the root and use it the instant I arrive at a node. Doing a node's work as soon as you land on it, before turning to the children, is pre-order.
The number I carry is simple: how much is still owed to reach the target. It starts at the full target. At each node I subtract this node's value — I "pay" it — and hand the leftover down to my children.
OR.def hasPathSum(node, need):
if node is None:
return False # no route through nothing
need -= node.val # pay this node on arrival (pre-order)
if node.left is None and node.right is None:
return need == 0 # a leaf: must land exactly on zero
return hasPathSum(node.left, need) or hasPathSum(node.right, need)One thing to hold onto: the empty-node check and the leaf check are different jobs — don't merge them. If you let an empty node answer need == 0, a node with one missing child could report success for a path that stops in mid-air at an internal node. The path must end at a leaf, so tie the success test to being a leaf, not to the total alone.
It is tempting to abandon a branch the moment need goes negative — "we already overshot." That is only safe when every value is non-negative. Here values go down to -1000, so a route that overshot by 50 can be rescued by a -50 further down. So we cannot prune on overshoot; we visit everything. (Quitting early on success is fine — or stops the instant one route works.)
O(N) time, O(H) stack space.
The number this problem needs — the running total — lives above each node, not below it. So instead of asking the children and combining on the way up, I carry the number down as an argument and use it the moment I arrive. That's the pre-order shape: work on arrival, then descend. The one neat touch is carrying what's still owed (counting down toward 0) rather than adding up a total and comparing at the end — one number in flight, not two. O(N) time, O(H) stack.
Path Sum Calculus
Subtraction Logic: Goal (22)
Subtracting node value from goal as we go deeper.
"Success if target becomes 0 at a node with no children."
Strategy
Recursive decomposition: `hasPathSum(root, sum) = hasPathSum(root.left, sum - val) || hasPathSum(root.right, sum - val)`.
"Reduce target at every step."