We need to find every path from the root of a binary tree down to a leaf node, and return each path as a string with node values separated by "->". A leaf is any node where both left and right children are null.
This maps directly to depth-first search. As the traversal descends from the root, it extends the current path. Every leaf it reaches marks a complete root-to-leaf path to record. The work of the problem is managing the path state: extending it on the way down and discarding the last node on the way back up before trying the other branch.
Number of nodes in [1, 100] → With at most 100 nodes, any traversal approach is fast enough. The constraint guarantees at least 1 node, so the tree is never empty, though defensive null checks cost nothing.-100 <= Node.val <= 100 → Values can be negative, so a valid path can look like "-1->-2". Standard integer-to-string conversion produces the minus sign correctly, so no special casing is needed.Walk the tree with DFS, building up the path string as we go. At each node, append the node's value to the current path. If the node is a leaf, that path is complete and goes into the result list. If not, recurse into the left and right children.
String concatenation creates a new string each time, so each recursive call works with its own copy of the path. When a call returns, the parent's path string is unchanged, which means no explicit undo step is needed.
The cost of that convenience is allocation. Copying the path at a node of depth d takes O(d) work, so total time grows with the sum of all node depths rather than the node count alone.
Approach 1 allocates a new path string at every node. The next approach keeps one mutable list for the whole traversal and builds a string only at the leaves.
Instead of creating a new string at every node, we can use a single mutable list to accumulate the path. We append the current node's value as we go down, and explicitly remove it when we backtrack. At a leaf, joining the list with "->" produces the path string, and that join is the only string allocation in the traversal.
The tradeoff is that the undo step is now ours to manage. With string concatenation, each call held a private copy of the path, so returning required no cleanup. With a shared mutable list, whatever a call appends it must remove before returning. This is the standard backtracking pattern.
Every call appends exactly one value on entry and removes exactly one value before returning. Between those two points, the list holds precisely the values from the root to the current node, so when the current node is a leaf, joining the list yields the complete root-to-leaf path.
Both approaches so far recurse, which limits the maximum tree depth to the size of the call stack. An explicit stack removes that limit.
We can simulate the recursive DFS with an explicit stack. Instead of letting the call stack manage the traversal, we push pairs of (node, current path) onto our own stack. At each step, we pop a pair, and if the node is a leaf, we add the path to the result. Otherwise, we push the children with the extended path.
Each stack entry carries its own path string, so there is no shared mutable state and no explicit backtracking. The tradeoff is that path strings sit on the stack alongside the nodes, which uses more memory than the backtracking list, and the per-node concatenation brings back the copying cost of Approach 1.