Deepest Leaves Sum
MedPrompt
root of a binary tree, return the sum of values of its deepest leaves.
Example 1:
Input: root = [1,2,3,4,5,null,6,7,null,null,null,null,8]
Output: 15Example 2:
Input: root = [6,7,8,2,7,1,3,9,null,1,4,null,null,null,5]
Output: 19
Constraints:
- The number of nodes in the tree is in the range
[1, 104]. 1 <= Node.val <= 100
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves two separate traversals of the tree. The first traversal is to determine the maximum depth of the tree. The second traversal then sums up the values of all nodes that are at this maximum depth.
Algorithm
- First Pass: Find Maximum Depth
- Define a recursive function
findMaxDepth(node). - The base case is a null node, which has a depth of 0.
- For a non-null node, the depth is
1 + max(findMaxDepth(node.left), findMaxDepth(node.right)). - Call this on the
rootto find themaxDepthof the entire tree.
- Define a recursive function
- Second Pass: Sum at Maximum Depth
- Define a second recursive function
sumAtDepth(node, currentDepth, maxDepth). - Initialize a sum variable to 0.
- Traverse the tree, passing the
currentDepth. - If
currentDepthequalsmaxDepth, add the node's value to the sum. - Recursively call for left and right children with
currentDepth + 1.
- Define a second recursive function
- Return the final sum.
Walkthrough
This method breaks the problem down into two simpler, sequential steps.
-
First Pass - Find Maximum Depth: We first need to know what the 'deepest' level is. We can find this by performing a standard DFS traversal. A recursive function
findMaxDepth(node)can compute the height of the subtree rooted atnode. The height of a null node is 0, and the height of a non-null node is 1 plus the maximum height of its left and right subtrees. By calling this on the root, we get the maximum depth of the entire tree. -
Second Pass - Sum Deepest Leaves: Once we have the
maxDepth, we traverse the tree again. This time, we use another recursive function, saysumAtDepth(node, currentDepth, maxDepth). We pass down the current depth of each node. If a node'scurrentDepthmatches themaxDepthwe found earlier, we add its value to a running total. After this second traversal is complete, the total will be our answer.
class Solution { int sum = 0; public int deepestLeavesSum(TreeNode root) { if (root == null) { return 0; } int maxDepth = findMaxDepth(root); sumAtDepth(root, 1, maxDepth); return sum; } private int findMaxDepth(TreeNode node) { if (node == null) { return 0; } return 1 + Math.max(findMaxDepth(node.left), findMaxDepth(node.right)); } private void sumAtDepth(TreeNode node, int currentDepth, int maxDepth) { if (node == null) { return; } if (currentDepth == maxDepth) { sum += node.val; } sumAtDepth(node.left, currentDepth + 1, maxDepth); sumAtDepth(node.right, currentDepth + 1, maxDepth); }}Complexity
Time
O(N), where N is the number of nodes. The first pass to find the depth takes O(N) and the second pass to sum the values also takes O(N). O(N) + O(N) = O(N).
Space
O(H), where H is the height of the tree, for the recursion stack. In the worst-case scenario of a skewed tree, H can be equal to N, making the space complexity O(N).
Trade-offs
Pros
Conceptually straightforward as it breaks the problem into two distinct, simpler subproblems.
Cons
Inefficient as it requires traversing the entire tree twice.
This approach optimizes the two-pass method by combining the two steps into a single traversal. We use a single DFS traversal to find the deepest leaves and sum their values simultaneously. We keep track of the maximum depth seen so far and the sum at that depth.
Algorithm
- Initialize two member variables:
maxDepth = -1anddeepestSum = 0. - Create a recursive DFS function
dfs(node, depth). - Base case: If
nodeis null, return. - If the current
depthis greater thanmaxDepth, it means we've found a new deepest level. UpdatemaxDepthtodepthand resetdeepestSumtonode.val. - Else if
depthis equal tomaxDepth, it means we've found another node at the current deepest level. Addnode.valtodeepestSum. - Recursively call
dfsfor the left and right children, incrementing the depth for each call (depth + 1). - Start the traversal with
dfs(root, 0). - Return
deepestSum.
Walkthrough
Instead of traversing the tree twice, we can gather all the necessary information in a single pass. We can perform a pre-order DFS traversal, keeping track of the current node's depth.
We use two global or member variables: maxDepth to store the maximum depth encountered so far, and deepestSum to store the sum of nodes at that maxDepth.
During the traversal, for each node at a certain depth:
- If
depth > maxDepth, we have found a new, deeper level. This means any previous sum is now invalid. We updatemaxDepthto the currentdepthand resetdeepestSumto the current node's value. - If
depth == maxDepth, we have found another node at the current deepest level. We simply add its value todeepestSum. - If
depth < maxDepth, we ignore the node as it's not at the deepest level.
We start the traversal from the root at depth 0. After the traversal is complete, deepestSum will hold the required result.
class Solution { private int maxDepth = -1; private int deepestSum = 0; public int deepestLeavesSum(TreeNode root) { dfs(root, 0); return deepestSum; } private void dfs(TreeNode node, int depth) { if (node == null) { return; } if (depth > maxDepth) { maxDepth = depth; deepestSum = node.val; } else if (depth == maxDepth) { deepestSum += node.val; } dfs(node.left, depth + 1); dfs(node.right, depth + 1); }}Complexity
Time
O(N), where N is the number of nodes, as each node is visited exactly once.
Space
O(H), where H is the height of the tree, for the recursion stack. In the worst-case scenario of a skewed tree, H can be equal to N, making the space complexity O(N).
Trade-offs
Pros
Efficient as it traverses the tree only once.
Combines finding the max depth and summing into a single, elegant recursive function.
Cons
Relies on member variables to maintain state across recursive calls, which can sometimes be less clean than passing state as parameters.
Recursive nature might lead to a stack overflow for extremely deep trees.
This is an iterative approach that is very natural for level-based tree problems. We traverse the tree level by level, from top to bottom, using Breadth-First Search (BFS). For each level, we calculate the sum of its nodes' values. The sum of the very last level we process will be our answer.
Algorithm
- If the
rootis null, return 0. - Initialize a
Queueand add therootto it. - Initialize a variable
levelSum = 0. - Loop while the queue is not empty:
- Get the number of nodes in the current level:
levelSize = queue.size(). - Reset
levelSum = 0. - Loop
levelSizetimes:- Dequeue a node,
currentNode. - Add
currentNode.valtolevelSum. - If
currentNode.leftis not null, enqueue it. - If
currentNode.rightis not null, enqueue it.
- Dequeue a node,
- Get the number of nodes in the current level:
- After the main loop finishes, the last computed
levelSumis the sum of the deepest level. Return it.
Walkthrough
Breadth-First Search (BFS), also known as level-order traversal, is a perfect fit for this problem. The algorithm processes the tree one level at a time.
We use a Queue to facilitate the traversal. We start by adding the root to the queue. Then, we loop as long as the queue is not empty. In each iteration of the main loop, we are processing one entire level of the tree.
We first find out how many nodes are on the current level (levelSize = queue.size()). We then reset a levelSum variable to 0. We loop levelSize times, dequeuing each node of the current level, adding its value to levelSum, and enqueuing its children for the next level's processing.
Because we calculate the sum for each level and overwrite it with the sum of the next level, the value of levelSum after the main loop terminates will be the sum of the very last, i.e., the deepest, level.
import java.util.LinkedList;import java.util.Queue; class Solution { public int deepestLeavesSum(TreeNode root) { if (root == null) { return 0; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); int levelSum = 0; while (!queue.isEmpty()) { int levelSize = queue.size(); levelSum = 0; // Reset sum for the new level for (int i = 0; i < levelSize; i++) { TreeNode currentNode = queue.poll(); levelSum += currentNode.val; if (currentNode.left != null) { queue.offer(currentNode.left); } if (currentNode.right != null) { queue.offer(currentNode.right); } } } return levelSum; }}Complexity
Time
O(N), where N is the number of nodes. Each node is enqueued and dequeued exactly once.
Space
O(W), where W is the maximum width of the tree. This is for the space used by the queue. In the worst case of a complete binary tree, the last level can contain roughly N/2 nodes, making the space complexity O(N).
Trade-offs
Pros
Iterative approach, which avoids the risk of stack overflow on very deep trees.
Very intuitive for level-based problems, as it processes the tree one level at a time.
Efficient single-pass traversal.
Cons
The space complexity can be high for wide trees. In the worst case (a complete binary tree), the queue can hold up to N/2 nodes.
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 deepestLeavesSum ( TreeNode root ) { Deque < TreeNode > q = new ArrayDeque <>(); q . offer ( root ); int ans = 0 ; while (! q . isEmpty ()) { ans = 0 ; for ( int n = q . size (); n > 0 ; -- n ) { root = q . pollFirst (); ans += root . val ; if ( root . left != null ) { q . offer ( root . left ); } if ( root . right != null ) { q . offer ( root . right ); } } } return ans ; } }/** * 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 deepestLeavesSum ( TreeNode * root ) { int ans = 0 ; queue < TreeNode *> q { { root } }; while ( ! q . empty ()) { ans = 0 ; for ( int n = q . size (); n ; -- n ) { root = q . front (); q . pop (); ans += root -> val ; if ( root -> left ) q . push ( root -> left ); if ( root -> right ) q . push ( root -> right ); } } return ans ; } };# 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 deepestLeavesSum ( self , root : Optional [ TreeNode ]) -> int : q = deque ([ root ]) while q : ans = 0 for _ in range ( len ( q )): root = q . popleft () ans += root . val if root . left : q . append ( root . left ) if root . right : q . append ( root . right ) return ansVideo 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.