We need to find every path from the root down to a leaf node whose node values add up to a given target.
Paths must go from root to leaf. Stopping at an internal node doesn't count, even if the sum happens to match at that point. We return all valid paths, not just one. And the node values can be negative, which means a path's running sum can decrease as we go deeper. That rules out any early termination like "stop exploring once the sum already exceeds the target," since a negative value further down could bring it back to the target.
The work is to explore every root-to-leaf path while tracking which nodes are on the current path and what the running sum is.
Number of nodes up to 5000 → The tree is small enough that the path-copying cost in some approaches stays acceptable, but we still want to avoid redundant work where we can.-1000 <= Node.val <= 1000 → Node values can be negative, so we cannot prune a branch when its running sum passes the target. A negative descendant could bring the sum back down.-1000 <= targetSum <= 1000 → The target can be negative, and the running-sum logic handles that without any special case.Depth-first search fits this problem: it walks one root-to-leaf route at a time, and at each node we can carry the path taken from the root down to that node. When we reach a leaf, we check whether the path sums to the target.
The simplest version creates a new copy of the path at every recursive call. Each node gets its own independent list, so one branch can never corrupt another's path. It is easy to reason about, at the cost of copying the path at every node.
targetSum as the remaining amount.remaining - node.val and the new path.Loading animation...
The copy at every node is the part worth removing. The next approach keeps a single shared path and undoes each step on the way back up.
Instead of copying the path at every node, we maintain a single path list that we modify in place. Before exploring a node's children, we add the node to the path. After both children are done, we remove the node. This add-then-remove step is backtracking.
This stays correct because the add and remove are paired with entering and leaving each node, which follows the call stack's last-in-first-out order. When DFS enters a node it appends to the path; when DFS finishes that node and returns to its caller it removes the last element. At any moment, the path holds exactly the nodes from the root down to the current node. The only copy happens when we reach a matching leaf, which is far rarer than visiting a node.
path list and a result list.path.path into result.remaining - node.val.path (backtrack).Loading animation...
Backtracking gives the best space usage, but it still relies on recursion and the call stack. The next approach replaces the call stack with an explicit one.
We can run the same DFS using an explicit stack instead of the call stack. Each stack entry holds the current node, the remaining sum, and the path up to that node. With no recursion, the depth is bounded by the explicit stack rather than the runtime's call-stack limit, which matters for a deeply skewed tree where recursion could overflow.
The cost is that each entry carries its own copy of the path, since there is no single shared list to add to and remove from. That brings back the same copying overhead as Approach 1, so this is not faster, just a recursion-free alternative.
(root, targetSum, [root.val]).(right, remaining - node.val, path + [right.val]) onto the stack.(left, remaining - node.val, path + [left.val]) onto the stack.Loading animation...