Sum Root to Leaf Numbers

Patrick Leaton
Problem Description

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 -> 3 represents the number 123.

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.

leaf node is a node with no children.

 

Example 1:

Input: root = [1,2,3]
Output: 25
Explanation:
The root-to-leaf path 1->2 represents the number 12.
The root-to-leaf path 1->3 represents the number 13.
Therefore, sum = 12 + 13 = 25.

Example 2:

Input: root = [4,9,0,5,1]
Output: 1026
Explanation:
The root-to-leaf path 4->9->5 represents the number 495.
The root-to-leaf path 4->9->1 represents the number 491.
The root-to-leaf path 4->0 represents the number 40.
Therefore, sum = 495 + 491 + 40 = 1026.

 

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.

 

The description was taken from https://leetcode.com/problems/sum-root-to-leaf-numbers/.

Problem Solution

#Backtracking
#O(N) Time, O(N) Space
class Solution:
    def sumNumbers(self, root: Optional[TreeNode]) -> int:
        paths = []
        output = 0
       
        def preorder(node:TreeNode, path:list) -> None:
            if not node:
                return
            if not node.left and not node.right:
                paths.append(path + [str(node.val)])
                return
            path.append(str(node.val))
            preorder(node.left, path)
            preorder(node.right, path)
            path.pop()
       
       
        preorder(root, [])
        for path in paths:
            output += int("".join(path))
        return output
 
#Math
#O(N) Time, O(N) Space
class Solution:
    def sumNumbers(self, root: Optional[TreeNode]) -> int:
        paths = 0
       
        def preorder(node:TreeNode, path:int) -> None:
            nonlocal paths
            if not node:
                return
            if not node.left and not node.right:
                paths += path * 10 + node.val
                return
            path = path * 10 + node.val
            preorder(node.left, path)
            preorder(node.right, path)       
           
        preorder(root, 0)
        return paths

Problem Explanation


Backtracking 

If a problem requires returning every combination of something, that is usually an indication that backtracking can be implemented.  Backtracking is a Depth-First Search algorithm where if a path doesn't lead to a valid solution, or it did and now needs to travel to a different solution, the last placement is reverted from the path so that a new placement can be circulated in.

Here, we can append each number along every root-to-leaf path to a list, then once a leaf or null node is hit, we will backtrack by popping every number off the list from the current path so that numbers from each additional path can be circulated in.

Once we have each valid path combination, we will just need to traverse the list of these and sum them together.


Let's start by creating a paths matrix to contain each valid path we come across.

We will also need an output number which will be the sum of each of these paths.

        paths = []
        output = 0


Next, we will make our preorder DFS function.  

It will take a tree node to be visited and a current path list as arguments.

        def preorder(node:TreeNode, path:list) -> None:

 

Backtracking functions usually have two base cases, a good and a bad.

Our bad base case will be if we hit a root node then we have exhausted a path and will return.

            if not node:
                return

 

Our good base case will be if we have no left or right children at the current node, then we have reached a leaf node.

            if not node.left and not node.right:

 

If this is the case, we will append the current node value to the current path then append the path to the paths array and return.

Here we are converting the node values to strings when we append them to the path just because it requires fewer steps to convert a list of strings to a number than a list of numbers in Python.  This is just a shortcut for us.

            if not node.left and not node.right:
                paths.append(path + [str(node.val)])
                return

 

If we are not at a leaf node, we will append the current node to the current path and continue the DFS on our left and right children.

            path.append(str(node.val))
            preorder(node.left, path)
            preorder(node.right, path)

 

Once the DFS yoyo has returned to the current function call, we will pop off the node value from the path.

If we don't do this, then node values will remain in the path as nodes higher in the tree are visited, resulting in an incorrect sum.

            path.pop()


Now that our helper function is built, we need to make an initial call on the root with an empty path.

        preorder(root, [])

 

For each path in the paths list, we will join the path to a string, convert that string into a number, then increment the output by that number.

        for path in paths:
            output += int("".join(path))

 

When we have finished with that, we will return the output.

        return output



Math 

Similar to some of the problems that require adding numbers in the form of strings, linked lists, etc., we can traverse digits of a number and join those digits together by multiplying the current number by ten to create a placeholder zero that the next digit will go into.

The idea is the same as the previous solution but here we won't have arrays that we need to convert into numbers and since we don't have external storage, we won't need to backtrack.

#Math
#O(N) Time, O(N) Space
class Solution:
    def sumNumbers(self, root: Optional[TreeNode]) -> int:
        paths = 0
       
        def preorder(node:TreeNode, path:int) -> None:
            nonlocal paths
            if not node:
                return
            if not node.left and not node.right:
                paths += path * 10 + node.val
                return
            path = path * 10 + node.val
            preorder(node.left, path)
            preorder(node.right, path)       
           
        preorder(root, 0)
        return paths