AlgoMaster Logo

Path Sum II

mediumFrequencyUpdated September 21, 2026

Understanding the Problem

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.

Key Constraints:

  • 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.

Approach 1: DFS with Path Copying

Intuition

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.

Algorithm

  1. Start at the root with the full targetSum as the remaining amount.
  2. At each node, create a new list that includes all previous nodes plus the current node.
  3. If the current node is a leaf and the remaining amount equals the node's value, add this path to the result.
  4. Otherwise, recurse into the left and right children with remaining - node.val and the new path.
  5. Since each call gets its own copy, there is nothing to undo when control returns to the parent.

Visualization and Code

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.

Approach 2: DFS with Backtracking (Optimal)

Intuition

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.

Algorithm

  1. Maintain a single path list and a result list.
  2. At each node, add the node's value to path.
  3. If the node is a leaf and the remaining sum equals the node's value, copy path into result.
  4. Otherwise, recurse into the left and right children, passing remaining - node.val.
  5. After the children return (or after recording a match), remove the last element from path (backtrack).

Visualization and Code

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.

Approach 3: Iterative DFS with Explicit Stack

Intuition

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.

Algorithm

  1. Initialize a stack with a tuple: (root, targetSum, [root.val]).
  2. While the stack is not empty, pop the top entry.
  3. If the node is a leaf and the remaining sum equals the node's value, add the path to the result.
  4. If the node has a right child, push (right, remaining - node.val, path + [right.val]) onto the stack.
  5. If the node has a left child, push (left, remaining - node.val, path + [left.val]) onto the stack.
  6. Continue until the stack is empty.

Visualization and Code

Loading animation...