Path Sum II
Given the root of a binary tree and a target sum, return every root-to-leaf path whose values add up to the target.
Open on LeetCode ↗Intuition
The trap is appending the live path list straight into your results. Lists in most languages are passed by reference, so if you push the same list object into the answer at every matching leaf, every entry ends up pointing at the exact same array -- and once you pop values off it on the way back up (which you must, to backtrack correctly), all your saved results silently turn into the same, usually empty, list. The fix is to append a copy of the path at the moment of the match, not the list itself. Keep one shared path list for the whole DFS, push a value going down, check the leaf condition, save a copy if it hits, then pop the value going back up regardless of whether it matched. That push-then-pop symmetry is the invariant: the path list must be exactly restored to its parent's state by the time you return to the parent.
Approach
Track a running path and remaining target
Do a DFS that carries two pieces of state: the list of values from the root down to the current node, and the remaining amount needed to hit the target. At each node, append the node's value to the path and subtract it from the remaining sum before recursing into children.
On a leaf, copy before you save
When both children are null, check whether remaining has hit exactly zero. If it has, this path is a valid answer -- but append path[:] (or list(path)), not path itself, because path keeps being mutated as the recursion continues to explore other branches.
Pop on every return, matched or not
After recursing into both children (or after handling a leaf), pop the last value off the path before returning to the caller. This undoes the push from this call and restores the path to exactly what the parent expects, whether or not this subtree produced a match.
Solution & live demo
Edge cases
Return an empty list immediately; there are no root-to-leaf paths.
The running sum can dip below zero and climb back to exactly zero at a leaf; only equality at a leaf counts, so keep summing all the way down rather than pruning early on a negative partial sum.
The root is also a leaf; the path [root.val] should be recorded as one of the results.
Because each result is a copy taken at its own leaf, two paths that share ancestors do not interfere with each other in the output even though they briefly shared the same path list in memory.