404. Sum of Left Leaves
Problem Description
You are given the root of a binary tree. Your task is to find and return the sum of all left leaves in the tree.
A leaf is defined as a node that has no children (both left and right children are null).
A left leaf is specifically a leaf node that is the left child of its parent node.
For example, in a binary tree, if a node has a left child that is a leaf (has no children of its own), then that left child's value should be included in the sum. However, if a node has a right child that is a leaf, that right child's value should NOT be included in the sum, as it's not a left leaf.
The goal is to traverse the entire tree, identify all nodes that are both:
- Leaf nodes (no children)
- Left children of their parent nodes
Then return the sum of the values of all such nodes. If there are no left leaves in the tree, return 0
.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this is a binary tree problem, a tree is a special type of graph (specifically, a connected acyclic graph). Binary trees can be considered as graphs where each node has at most two children.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree. We have a root node, and each node has at most two children (left and right).
DFS
- Yes: We arrive at DFS (Depth-First Search) as our algorithm choice.
Conclusion: The flowchart suggests using a DFS approach for solving the Sum of Left Leaves problem.
This makes sense because:
- We need to traverse the entire tree to find all left leaves
- We need to check each node's relationship with its parent (to determine if it's a left child)
- We need to verify if each node is a leaf (has no children)
- DFS allows us to recursively explore each subtree while maintaining the parent-child relationship context
The DFS pattern perfectly fits this problem as we can:
- Recursively traverse the tree
- Keep track of whether we're exploring a left or right subtree
- Check leaf conditions at each node
- Accumulate the sum of values that meet our criteria (left leaves)
Intuition
When we need to find the sum of all left leaves, we need to identify two key properties for each node: is it a leaf, and is it a left child of its parent?
The challenge is that a node doesn't know if it's a left or right child - only its parent knows this relationship. This means we need to check a node's children to determine if they are left leaves, rather than checking the node itself.
As we traverse the tree using DFS, at each node we can:
- Look at our left child - if it exists and is a leaf (has no children), then we've found a left leaf and should add its value to our sum
- Look at our right child - even if it's a leaf, we don't add it since it's not a left leaf
The recursive nature of DFS fits perfectly here. We can break down the problem into smaller subproblems:
- Calculate the sum of left leaves in the right subtree
- Check if the left child is a left leaf - if yes, add its value; if no, calculate the sum of left leaves in the left subtree
- Combine these results
A clever observation in the code is the condition root.left.left == root.left.right
. When both children of a node are None
, this condition evaluates to True
(since None == None
), elegantly identifying a leaf node without explicitly checking if both children are None
.
This approach ensures we visit every node exactly once while maintaining the context needed to identify left leaves, giving us an efficient O(n)
solution where n
is the number of nodes in the tree.
Solution Approach
The solution implements a recursive DFS approach to traverse the binary tree and identify left leaves.
The algorithm starts by checking if the root
is null. If it is, we return 0
since there are no nodes to process.
For a non-null root, we begin by recursively calculating the sum of all left leaves in the right subtree with self.sumOfLeftLeaves(root.right)
and store this in our answer variable ans
. We process the right subtree first, but the order doesn't matter since we're ultimately summing all left leaves.
Next, we examine the left child of the current node. If root.left
exists, we need to determine if it's a leaf node:
- The condition
root.left.left == root.left.right
cleverly checks if both children areNone
. When a node is a leaf, both its left and right children areNone
, making this comparison evaluate toTrue
- If the left child is indeed a leaf, we add its value to
ans
withans += root.left.val
- If the left child is not a leaf (has at least one child), we recursively calculate the sum of left leaves in the left subtree with
ans += self.sumOfLeftLeaves(root.left)
The recursive calls continue to break down the tree into smaller subtrees, each following the same logic:
- Process the right subtree recursively
- Check if the left child is a left leaf or needs further exploration
- Return the accumulated sum
This approach ensures every node is visited exactly once, giving us a time complexity of O(n)
where n
is the number of nodes. The space complexity is O(h)
where h
is the height of the tree, due to the recursive call stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the algorithm identifies and sums left leaves.
Consider this binary tree:
3 / \ 9 20 / \ 15 7
Step 1: Start at root (3)
- First, recursively process the right subtree (node 20)
- Then check the left child (node 9)
Step 2: Process right subtree from root (node 20)
- Recursively process its right subtree (node 7)
- Node 7 has no children, so it returns 0
- Check its left child (node 15)
- Node 15 has no children (
15.left == 15.right
→None == None
→True
) - Node 15 IS a left leaf! Add its value: 15
- Node 15 has no children (
- Return 15 from this subtree
Step 3: Back at root (3), check left child (node 9)
- Node 9 exists and has no children (
9.left == 9.right
→None == None
→True
) - Node 9 IS a left leaf! Add its value: 9
Step 4: Combine results
- Sum from right subtree: 15
- Sum from left child: 9
- Total: 15 + 9 = 24
The algorithm correctly identifies nodes 9 and 15 as left leaves (both are leaf nodes AND left children of their parents), while excluding node 7 (which is a leaf but a right child). The final answer is 24.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8class Solution:
9 def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
10 # Base case: empty tree has no left leaves
11 if root is None:
12 return 0
13
14 # Initialize sum with left leaves from right subtree
15 total_sum = self.sumOfLeftLeaves(root.right)
16
17 # Check if there's a left child
18 if root.left:
19 # Check if left child is a leaf node (both its children are None)
20 if root.left.left is None and root.left.right is None:
21 # Add the value of this left leaf
22 total_sum += root.left.val
23 else:
24 # Left child is not a leaf, recursively process left subtree
25 total_sum += self.sumOfLeftLeaves(root.left)
26
27 return total_sum
28
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Calculates the sum of all left leaves in a binary tree.
19 * A left leaf is a leaf node that is the left child of its parent.
20 *
21 * @param root The root of the binary tree
22 * @return The sum of all left leaves' values
23 */
24 public int sumOfLeftLeaves(TreeNode root) {
25 // Base case: empty tree has no left leaves
26 if (root == null) {
27 return 0;
28 }
29
30 // Initialize sum with the result from the right subtree
31 int sum = sumOfLeftLeaves(root.right);
32
33 // Check if there's a left child
34 if (root.left != null) {
35 // Check if the left child is a leaf node
36 // A node is a leaf if both its children are null
37 if (root.left.left == null && root.left.right == null) {
38 // Add the left leaf's value to the sum
39 sum += root.left.val;
40 } else {
41 // If left child is not a leaf, recursively process the left subtree
42 sum += sumOfLeftLeaves(root.left);
43 }
44 }
45
46 return sum;
47 }
48}
49
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Calculates the sum of all left leaves in a binary tree.
16 * A left leaf is a leaf node that is the left child of its parent.
17 *
18 * @param root The root node of the binary tree
19 * @return The sum of values of all left leaves
20 */
21 int sumOfLeftLeaves(TreeNode* root) {
22 // Base case: if the tree is empty, return 0
23 if (!root) {
24 return 0;
25 }
26
27 // Initialize sum with the result from the right subtree
28 // (right subtree may contain left leaves of its own)
29 int sum = sumOfLeftLeaves(root->right);
30
31 // Check if there's a left child
32 if (root->left) {
33 // Check if the left child is a leaf node (no children)
34 if (!root->left->left && !root->left->right) {
35 // If it's a leaf, add its value to the sum
36 sum += root->left->val;
37 } else {
38 // If it's not a leaf, recursively process the left subtree
39 sum += sumOfLeftLeaves(root->left);
40 }
41 }
42
43 return sum;
44 }
45};
46
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Calculates the sum of all left leaves in a binary tree.
17 * A left leaf is a leaf node that is the left child of its parent.
18 *
19 * @param root - The root node of the binary tree
20 * @returns The sum of all left leaf values
21 */
22function sumOfLeftLeaves(root: TreeNode | null): number {
23 // Base case: empty tree has no left leaves
24 if (!root) {
25 return 0;
26 }
27
28 // Start by recursively calculating sum of left leaves in right subtree
29 let sum: number = sumOfLeftLeaves(root.right);
30
31 // Check if there's a left child
32 if (root.left) {
33 // Check if the left child is a leaf node
34 // (both its children are null, making them equal)
35 if (root.left.left === root.left.right) {
36 // Add the value of this left leaf to our sum
37 sum += root.left.val;
38 } else {
39 // Left child is not a leaf, recursively process its subtree
40 sum += sumOfLeftLeaves(root.left);
41 }
42 }
43
44 return sum;
45}
46
Time and Space Complexity
The time complexity is O(n)
, where n
is the number of nodes in the binary tree. This is because the algorithm visits each node exactly once through the recursive traversal. At each node, it performs constant time operations (checking if a node exists, checking if it's a leaf, and adding to the sum).
The space complexity is O(n)
in the worst case. This occurs when the tree is completely unbalanced (e.g., a skewed tree where each node has only one child), resulting in a recursion depth equal to the number of nodes. In the best case of a balanced tree, the space complexity would be O(log n)
due to the recursion call stack, but we consider the worst-case scenario for space complexity analysis.
Note: The condition root.left.left == root.left.right
is checking if both children are None
(since None == None
evaluates to True
), which identifies a leaf node.
Common Pitfalls
1. Incorrectly Identifying Left Leaves
Pitfall: A common mistake is incorrectly identifying which nodes qualify as "left leaves." Developers often make one of these errors:
- Including the root node if it's a leaf (the root cannot be a left leaf since it has no parent)
- Including all leaf nodes regardless of whether they're left or right children
- Only checking if a node is on the left side of the tree, rather than checking if it's specifically a left child AND a leaf
Example of Incorrect Logic:
# WRONG: This counts all leaves, not just left leaves
def sumOfLeftLeaves(self, root):
if not root:
return 0
if not root.left and not root.right: # This is a leaf
return root.val # WRONG: might not be a LEFT leaf
return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)
Solution: Always check from the parent's perspective. A node is a left leaf only if:
- It is the left child of its parent
- It has no children of its own
The correct approach maintains context about whether we're examining a left or right child:
def sumOfLeftLeaves(self, root):
def dfs(node, is_left):
if not node:
return 0
# Check if this is a left leaf
if not node.left and not node.right and is_left:
return node.val
# Recursively process children, marking left child appropriately
return dfs(node.left, True) + dfs(node.right, False)
return dfs(root, False) # Root is not a left child
2. Using Ambiguous Leaf Detection
Pitfall: The original solution uses root.left.left == root.left.right
to check if a node is a leaf. While this works when both children are None
, it can be confusing and may lead to errors if the tree implementation changes or if someone misunderstands the logic.
Solution: Use explicit and clear conditions:
# Clear and explicit leaf check if root.left and root.left.left is None and root.left.right is None: total_sum += root.left.val
This makes the code more readable and less prone to misinterpretation.
3. Edge Case Handling
Pitfall: Not properly handling edge cases such as:
- A tree with only one node (root only)
- A tree where all leaves are right children
- A skewed tree (all nodes form a single path)
Solution: Ensure your base cases and recursive logic handle these scenarios:
def sumOfLeftLeaves(self, root):
if not root:
return 0
# Single node tree - root cannot be a left leaf
if not root.left and not root.right:
return 0
total = 0
# Only process left child if it exists
if root.left:
if not root.left.left and not root.left.right:
total += root.left.val
else:
total += self.sumOfLeftLeaves(root.left)
# Always process right subtree for potential left leaves within it
total += self.sumOfLeftLeaves(root.right)
return total
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!