Sum Root to Leaf Numbers
MedPrompt
You are given the root of a binary tree containing digits from 0 to 9 only.
Each root-to-leaf path in the tree represents a number.
- For example, the root-to-leaf path
1 -> 2 -> 3represents the number123.
Return the total sum of all root-to-leaf numbers. Test cases are generated so that the answer will fit in a 32-bit integer.
A leaf node is a node with no children.
Example 1:
1->2Example 2:
4->9->5
Constraints:
- The number of nodes in the tree is in the range
[1, 1000]. 0 <= Node.val <= 9- The depth of the tree will not exceed
10.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach first generates all root-to-leaf paths, stores them, and then iterates through these paths to calculate the corresponding numbers and their sum. It's straightforward but inefficient due to high space usage.
Algorithm
- Initialize an empty list,
allPaths, to store string representations of the numbers for each root-to-leaf path. - Define a recursive helper function
findPaths(node, currentPathString, allPaths). - In the helper function, if the current
nodeis null, return. - Append the current node's value to
currentPathString. - If the current
nodeis a leaf (both left and right children are null), add thecurrentPathStringto theallPathslist. - Recursively call the helper function for the left and right children.
- After the initial call to
findPathson the root completes, iterate throughallPaths. - For each string in
allPaths, convert it to an integer and add it to atotalSum. - Return
totalSum.
Walkthrough
The core idea is to separate the problem into two distinct steps: path finding and summation.
-
Path Finding: A Depth-First Search (DFS) traversal is used to find all paths from the root to every leaf node. We use a helper function that maintains the current path from the root. When a leaf is encountered, the current path is complete and is added to a list of all paths.
-
Summation: After the traversal is complete, we have a list of all paths (where each path is a list of digits). We iterate through this list. For each path, we convert the sequence of digits into an integer. For example, the path
[4, 9, 5]is converted to the number 495. These numbers are then added to a running total.
Here is a code snippet illustrating this approach:
import java.util.ArrayList;import java.util.List; class Solution { public int sumNumbers(TreeNode root) { List<String> allPaths = new ArrayList<>(); findPaths(root, "", allPaths); int totalSum = 0; for (String path : allPaths) { totalSum += Integer.parseInt(path); } return totalSum; } private void findPaths(TreeNode node, String currentPath, List<String> allPaths) { if (node == null) { return; } currentPath += node.val; if (node.left == null && node.right == null) { allPaths.add(currentPath); return; } findPaths(node.left, currentPath, allPaths); findPaths(node.right, currentPath, allPaths); }}Complexity
Time
O(N*H)
Space
O(N*H)
Trade-offs
Pros
The logic is separated into two clear, understandable steps: finding paths and then summing them.
It's a direct translation of the problem statement.
Cons
Very inefficient in terms of space. It requires storing all root-to-leaf paths, which can be memory-intensive for large trees.
The time complexity is also suboptimal due to the overhead of creating and storing path strings/lists and then iterating over them again.
A much more efficient solution involves calculating the path numbers on-the-fly during a single Depth-First Search (DFS) traversal. This avoids the overhead of storing all paths, leading to optimal time and space complexity.
Algorithm
- Define a recursive helper function,
dfs(node, currentSum), which returns the sum of numbers for the subtree rooted atnode. - The
currentSumparameter holds the numerical value of the path from the root to the parent ofnode. - Base Case: If
nodeis null, it contributes nothing to the sum, so return 0. - Update the sum for the current path:
currentSum = currentSum * 10 + node.val. - Leaf Node Check: If the current
nodeis a leaf (both children are null), it signifies the end of a path. Return thecurrentSum. - Recursive Step: If it's not a leaf, recursively call
dfsfor the left and right children with the updatedcurrentSum. Return the sum of the results from these two calls:dfs(node.left, currentSum) + dfs(node.right, currentSum). - The main function initiates the process by calling
dfs(root, 0).
Walkthrough
This approach uses a pre-order traversal pattern. We maintain the numerical value of the path from the root to the current node's parent. As we descend to a child node, we update this value.
The formula to update the number at each step is newNumber = currentNumber * 10 + node.val. For example, if we are at node 9 and the path from the root was 4, the currentNumber passed to 9 would be 4. The new number becomes 4 * 10 + 9 = 49.
When we reach a leaf node, the number formed along that path is complete. We return this number. For non-leaf nodes, we recursively call the function for the left and right children and sum their results. This effectively sums up all the numbers from all leaf nodes below.
Here is the implementation:
class Solution { public int sumNumbers(TreeNode root) { return dfs(root, 0); } private int dfs(TreeNode node, int currentSum) { if (node == null) { return 0; } currentSum = currentSum * 10 + node.val; if (node.left == null && node.right == null) { return currentSum; } int leftSum = dfs(node.left, currentSum); int rightSum = dfs(node.right, currentSum); return leftSum + rightSum; }}Complexity
Time
O(N)
Space
O(H)
Trade-offs
Pros
Optimal time complexity as it visits each node only once.
Optimal space complexity, using space proportional to the tree's height for the recursion stack.
Elegant and concise solution.
Cons
On extremely deep trees (not applicable with this problem's constraints), deep recursion could potentially lead to a stack overflow error.
This is an iterative counterpart to the recursive DFS solution. It uses an explicit stack to manage the traversal, achieving the same optimal performance while being immune to recursion depth limits.
Algorithm
- If the root is null, return 0.
- Initialize
totalSum = 0. - Create a stack to store pairs of
(TreeNode, currentNumber). - Push the initial pair
(root, 0)onto the stack. - Loop while the stack is not empty:
- Pop a pair
(node, number)from the stack. - Calculate the new number for the path to this node:
newNumber = number * 10 + node.val. - If
nodeis a leaf, addnewNumbertototalSum. - If
nodehas a right child, push(node.right, newNumber)to the stack. - If
nodehas a left child, push(node.left, newNumber)to the stack.
- Pop a pair
- Return
totalSum.
Walkthrough
Instead of relying on the call stack for recursion, we use our own stack. The stack will store pairs of (TreeNode, currentNumber).
We start by pushing the root node and an initial number 0 onto the stack. The main loop continues as long as the stack is not empty. In each iteration, we pop a node and its corresponding path number. We calculate the new number for the path ending at this node. If the node is a leaf, we add this number to our total sum. Otherwise, we push its children (if they exist) onto the stack, along with the new path number. To maintain the pre-order traversal logic (process node, then left, then right), we push the right child onto the stack before the left child.
Here is the implementation:
import java.util.Stack; // Helper class to store a node and the sum up to it.class Pair { TreeNode node; int val; Pair(TreeNode node, int val) { this.node = node; this.val = val; }} class Solution { public int sumNumbers(TreeNode root) { if (root == null) return 0; int totalSum = 0; Stack<Pair> stack = new Stack<>(); stack.push(new Pair(root, 0)); while (!stack.isEmpty()) { Pair current = stack.pop(); TreeNode node = current.node; int currentSum = current.val; currentSum = currentSum * 10 + node.val; if (node.left == null && node.right == null) { totalSum += currentSum; } if (node.right != null) { stack.push(new Pair(node.right, currentSum)); } if (node.left != null) { stack.push(new Pair(node.left, currentSum)); } } return totalSum; }}Complexity
Time
O(N)
Space
O(H) on average, O(N) in the worst case
Trade-offs
Pros
Avoids recursion, thus preventing stack overflow errors on very deep trees.
Maintains the same optimal O(N) time complexity as the recursive solution.
Cons
The code can be slightly more verbose and less intuitive than the recursive version.
Worst-case space complexity can be O(N) for a wide, complete tree, whereas the recursive version's space is always bounded by the tree's height O(H).
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public int sumNumbers ( TreeNode root ) { return dfs ( root , 0 ); } private int dfs ( TreeNode root , int s ) { if ( root == null ) { return 0 ; } s = s * 10 + root . val ; if ( root . left == null && root . right == null ) { return s ; } return dfs ( root . left , s ) + dfs ( root . right , s ); } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number} */ var sumNumbers = function (root) { function dfs(root, s) { if (!root) return 0; s = s * 10 + root.val; if (!root.left && !root.right) return s; return dfs(root.left, s) + dfs(root.right, s); } return dfs(root, 0); };/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: int sumNumbers ( TreeNode * root ) { function < int ( TreeNode * , int ) > dfs = [ & ]( TreeNode * root , int s ) -> int { if ( ! root ) return 0 ; s = s * 10 + root -> val ; if ( ! root -> left && ! root -> right ) return s ; return dfs ( root -> left , s ) + dfs ( root -> right , s ); }; return dfs ( root , 0 ); } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def sumNumbers ( self , root : Optional [ TreeNode ]) -> int : def dfs ( root , s ): if root is None : return 0 s = s * 10 + root . val if root . left is None and root . right is None : return s return dfs ( root . left , s ) + dfs ( root . right , s ) return dfs ( root , 0 )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.