1973. Count Nodes Equal to Sum of Descendants 🔒
Problem Description
You are given the root of a binary tree. Your task is to count how many nodes in the tree have a value that equals the sum of all their descendant nodes' values.
A descendant of a node is any node that can be reached by traveling downward from that node (following the path toward the leaves). This includes the node's children, grandchildren, and so on, all the way to the leaf nodes.
For nodes that have no descendants (leaf nodes), the sum of descendants is considered to be 0
.
The solution uses a depth-first search (DFS) approach that traverses the tree from bottom to top. For each node, it:
- Recursively calculates the sum of all values in the left subtree
- Recursively calculates the sum of all values in the right subtree
- Checks if the sum of these two subtree values equals the current node's value
- If they are equal, increments the counter
- Returns the total sum of the current subtree (current node value + left subtree sum + right subtree sum) for use by parent nodes
For example, if a node has value 10
and its left subtree nodes sum to 4
and right subtree nodes sum to 6
, then 4 + 6 = 10
equals the node's value, so this node would be counted. The function would return 10 + 4 + 6 = 20
as the sum for this subtree.
The nonlocal
keyword is used to modify the ans
variable from the outer scope, allowing the nested function to update the count of valid nodes as it traverses the tree.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves a binary tree, which is a special type of graph with nodes connected by edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure where we have a root node and each node can have at most two children.
DFS
- We arrive at DFS: Since we're dealing with a tree structure, the flowchart directly leads us to use Depth-First Search (DFS).
Why DFS is the right choice:
- We need to calculate the sum of all descendant nodes for each node in the tree
- This requires visiting child nodes first before we can determine if a parent node's value equals the sum of its descendants
- DFS with post-order traversal (process children before parent) is perfect for this bottom-up calculation
- As we return from recursive calls, we can accumulate the sums from subtrees and check the condition at each node
Conclusion: The flowchart correctly identifies that for a tree-based problem requiring information from descendants to make decisions about ancestors, DFS is the optimal approach. The recursive nature of DFS allows us to naturally compute subtree sums while traversing back up the tree.
Intuition
When we need to check if a node's value equals the sum of all its descendants, we face a key challenge: we can't know the sum of descendants until we've visited all of them. This naturally suggests a bottom-up approach where we process leaf nodes first and work our way back to the root.
Think about it this way: if we're standing at a node, we need to know:
- The sum of all values in the left subtree (all left descendants)
- The sum of all values in the right subtree (all right descendants)
Only after obtaining these two pieces of information can we check if left_sum + right_sum == node.val
.
This points us toward a post-order traversal pattern where we:
- First recursively process the left child
- Then recursively process the right child
- Finally process the current node
The beauty of this recursive approach is that each recursive call can return the total sum of its subtree. When we're at a node, the recursive calls to its children give us exactly what we need - the sum of all descendants in each subtree.
For example, when processing a node with value 10
:
- The left recursive call returns
4
(sum of all left descendants) - The right recursive call returns
6
(sum of all right descendants) - We check if
4 + 6 == 10
and increment our counter if true - We return
10 + 4 + 6 = 20
to our parent, representing the total sum of this entire subtree
This way, each node gets the information it needs from its children, makes its decision, and passes useful information up to its parent - a classic recursive pattern that elegantly solves the problem in a single tree traversal.
Learn more about Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The implementation uses a recursive DFS function that processes the tree in a post-order manner. Let's walk through the key components:
Main Structure:
- We define a nested
dfs
function that will traverse the tree and return the sum of each subtree - We use a
nonlocal
variableans
to keep track of the count of valid nodes across all recursive calls
Base Case:
if root is None: return 0
When we encounter a None
node (empty subtree), we return 0
as the sum, which naturally handles leaf nodes whose children are None
.
Recursive Processing:
l, r = dfs(root.left), dfs(root.right)
We recursively calculate:
l
: the sum of all nodes in the left subtreer
: the sum of all nodes in the right subtree
These recursive calls happen first (before processing the current node), ensuring we have the descendant information we need.
Checking the Condition:
if l + r == root.val: nonlocal ans ans += 1
After obtaining the sums from both subtrees, we check if their sum equals the current node's value. If it does, we increment our counter. The nonlocal
keyword allows us to modify the ans
variable from the outer scope.
Returning the Subtree Sum:
return root.val + l + r
This is crucial - we return the total sum of the current subtree (current node's value plus both subtree sums). This returned value becomes the l
or r
value for the parent node's calculation.
Time and Space Complexity:
- Time:
O(n)
wheren
is the number of nodes, as we visit each node exactly once - Space:
O(h)
whereh
is the height of the tree, representing the recursion stack depth (worst caseO(n)
for a skewed tree)
The elegance of this solution lies in how it combines the traversal with the computation - each recursive call serves dual purposes: checking the condition for the current node and providing necessary information to parent nodes.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the algorithm works. Consider this binary tree:
12 / \ 3 9 / \ \ 1 2 5
We want to count nodes whose value equals the sum of all their descendants.
Step-by-step execution:
-
Start at root (12): The DFS begins but immediately recurses to process children first.
-
Process left subtree of 12 (node 3):
- First recurse to node 3's children
- Node 1 (leaf):
- Left child: None → returns 0
- Right child: None → returns 0
- Check: 0 + 0 = 0, but node.val = 1 (not equal, don't count)
- Returns: 1 + 0 + 0 = 1
- Node 2 (leaf):
- Left child: None → returns 0
- Right child: None → returns 0
- Check: 0 + 0 = 0, but node.val = 2 (not equal, don't count)
- Returns: 2 + 0 + 0 = 2
- Back to Node 3:
- Left subtree sum: 1 (from node 1)
- Right subtree sum: 2 (from node 2)
- Check: 1 + 2 = 3, and node.val = 3 ✓ (equal, count it!)
- ans = 1
- Returns: 3 + 1 + 2 = 6
-
Process right subtree of 12 (node 9):
- First recurse to node 9's children
- Node 5 (leaf):
- Left child: None → returns 0
- Right child: None → returns 0
- Check: 0 + 0 = 0, but node.val = 5 (not equal, don't count)
- Returns: 5 + 0 + 0 = 5
- Back to Node 9:
- Left subtree sum: 0 (no left child)
- Right subtree sum: 5 (from node 5)
- Check: 0 + 5 = 5, but node.val = 9 (not equal, don't count)
- Returns: 9 + 0 + 5 = 14
-
Finally process root (12):
- Left subtree sum: 6 (sum of nodes 3, 1, 2)
- Right subtree sum: 14 (sum of nodes 9, 5)
- Check: 6 + 14 = 20, but node.val = 12 (not equal, don't count)
- Returns: 12 + 6 + 14 = 32 (total tree sum)
Result: ans = 1 (only node 3 satisfies the condition)
The key insight is how each recursive call returns the sum of its entire subtree, which becomes exactly the "sum of descendants" needed by the parent node. This bottom-up information flow allows us to check each node's condition in a single traversal.
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
8from typing import Optional
9
10class Solution:
11 def equalToDescendants(self, root: Optional[TreeNode]) -> int:
12 """
13 Count the number of nodes whose value equals the sum of their descendants.
14
15 Args:
16 root: The root of the binary tree
17
18 Returns:
19 The count of nodes whose value equals the sum of all their descendants
20 """
21
22 def dfs(node: Optional[TreeNode]) -> int:
23 """
24 Perform post-order traversal to calculate sum of subtree values.
25
26 Args:
27 node: Current node being processed
28
29 Returns:
30 Sum of all values in the subtree rooted at this node
31 """
32 # Base case: empty node contributes 0 to the sum
33 if node is None:
34 return 0
35
36 # Recursively calculate sum of left and right subtrees
37 left_sum = dfs(node.left)
38 right_sum = dfs(node.right)
39
40 # Check if current node's value equals sum of its descendants
41 if left_sum + right_sum == node.val:
42 nonlocal result_count
43 result_count += 1
44
45 # Return total sum including current node's value
46 return node.val + left_sum + right_sum
47
48 # Initialize counter for nodes meeting the condition
49 result_count = 0
50
51 # Start DFS traversal from root
52 dfs(root)
53
54 return result_count
55
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 // Counter to track nodes whose value equals sum of descendants
18 private int count;
19
20 /**
21 * Counts the number of nodes whose value equals the sum of all their descendants.
22 *
23 * @param root The root of the binary tree
24 * @return The count of nodes satisfying the condition
25 */
26 public int equalToDescendants(TreeNode root) {
27 count = 0;
28 calculateSubtreeSum(root);
29 return count;
30 }
31
32 /**
33 * Performs post-order traversal to calculate subtree sums.
34 * For each node, checks if its value equals the sum of its left and right subtrees.
35 *
36 * @param node The current node being processed
37 * @return The sum of the current node's value and all its descendants
38 */
39 private int calculateSubtreeSum(TreeNode node) {
40 // Base case: null node contributes 0 to the sum
41 if (node == null) {
42 return 0;
43 }
44
45 // Recursively calculate sum of left subtree
46 int leftSum = calculateSubtreeSum(node.left);
47
48 // Recursively calculate sum of right subtree
49 int rightSum = calculateSubtreeSum(node.right);
50
51 // Check if current node's value equals sum of its descendants
52 if (leftSum + rightSum == node.val) {
53 count++;
54 }
55
56 // Return total sum including current node and all descendants
57 return node.val + leftSum + rightSum;
58 }
59}
60
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 * Counts the number of nodes whose value equals the sum of all descendant values
16 * @param root - The root of the binary tree
17 * @return The count of nodes where node value equals sum of all descendants
18 */
19 int equalToDescendants(TreeNode* root) {
20 int count = 0;
21
22 // Lambda function for depth-first search traversal
23 // Returns the sum of the current node value and all its descendants
24 function<long long(TreeNode*)> dfs = [&](TreeNode* node) -> long long {
25 // Base case: empty node contributes 0 to the sum
26 if (!node) {
27 return 0;
28 }
29
30 // Recursively calculate sum of left and right subtrees
31 long long leftSum = dfs(node->left);
32 long long rightSum = dfs(node->right);
33
34 // Check if current node's value equals sum of its descendants
35 // Increment count if condition is satisfied
36 if (leftSum + rightSum == node->val) {
37 count++;
38 }
39
40 // Return total sum including current node and all descendants
41 return node->val + leftSum + rightSum;
42 };
43
44 // Start DFS traversal from root
45 dfs(root);
46
47 return count;
48 }
49};
50
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.left = (left === undefined ? null : left);
11 this.right = (right === undefined ? null : right);
12 }
13}
14
15/**
16 * Counts the number of nodes whose value equals the sum of all descendant values
17 * @param root - The root of the binary tree
18 * @return The count of nodes where node value equals sum of all descendants
19 */
20function equalToDescendants(root: TreeNode | null): number {
21 // Initialize counter for nodes that satisfy the condition
22 let count = 0;
23
24 /**
25 * Performs depth-first search traversal of the tree
26 * @param node - Current node being processed
27 * @return The sum of the current node value and all its descendants
28 */
29 const dfs = (node: TreeNode | null): number => {
30 // Base case: null node contributes 0 to the sum
31 if (!node) {
32 return 0;
33 }
34
35 // Recursively calculate the sum of left subtree
36 const leftSum = dfs(node.left);
37
38 // Recursively calculate the sum of right subtree
39 const rightSum = dfs(node.right);
40
41 // Check if current node's value equals the sum of its descendants
42 // Increment count if the condition is satisfied
43 if (leftSum + rightSum === node.val) {
44 count++;
45 }
46
47 // Return the total sum including current node and all descendants
48 return node.val + leftSum + rightSum;
49 };
50
51 // Start the DFS traversal from the root node
52 dfs(root);
53
54 // Return the final count of nodes that satisfy the condition
55 return count;
56}
57
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree. The algorithm performs a depth-first search (DFS) traversal that visits each node exactly once. At each node, it performs constant time operations (comparison and addition), so the overall time complexity is linear with respect to the number of nodes.
Space Complexity: O(h)
where h
is the height of the binary tree. The space complexity is determined by the recursive call stack during the DFS traversal. In the worst case (a skewed tree), the height could be O(n)
, making the space complexity O(n)
. In the best case (a balanced tree), the height would be O(log n)
, making the space complexity O(log n)
. The additional space used for the ans
variable is O(1)
and doesn't affect the overall space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem Definition
A common mistake is confusing "sum of descendants" with "sum of all nodes in the subtree including the node itself." The problem specifically asks for the sum of descendants only, which excludes the current node's value.
Incorrect interpretation:
# WRONG: Including the node itself in the comparison
def dfs(node):
if node is None:
return 0
left_sum = dfs(node.left)
right_sum = dfs(node.right)
subtree_sum = node.val + left_sum + right_sum
# This compares against the entire subtree sum, not just descendants
if subtree_sum == node.val: # This would only be true when descendants sum to 0
nonlocal result_count
result_count += 1
return subtree_sum
Correct approach:
# RIGHT: Comparing node value with sum of descendants only if left_sum + right_sum == node.val: # Descendants sum excludes current node nonlocal result_count result_count += 1
2. Forgetting to Declare nonlocal
for the Counter Variable
Without the nonlocal
declaration, Python treats result_count
as a local variable when you try to modify it, leading to an UnboundLocalError
.
Incorrect code:
def equalToDescendants(self, root):
result_count = 0
def dfs(node):
if node is None:
return 0
left_sum = dfs(node.left)
right_sum = dfs(node.right)
if left_sum + right_sum == node.val:
result_count += 1 # ERROR: UnboundLocalError
return node.val + left_sum + right_sum
dfs(root)
return result_count
Solution:
Always declare nonlocal
before modifying an outer scope variable:
if left_sum + right_sum == node.val: nonlocal result_count # Declare before modifying result_count += 1
3. Incorrect Return Value from DFS Function
Some might mistakenly return only the count or forget to include all components in the sum, breaking the logic for parent nodes.
Common mistakes:
# WRONG: Returning the count instead of sum
def dfs(node):
if node is None:
return 0
left_sum = dfs(node.left)
right_sum = dfs(node.right)
if left_sum + right_sum == node.val:
return 1 # WRONG: Should return sum, not count
return 0
# WRONG: Forgetting to include node's own value in return
def dfs(node):
if node is None:
return 0
left_sum = dfs(node.left)
right_sum = dfs(node.right)
if left_sum + right_sum == node.val:
nonlocal result_count
result_count += 1
return left_sum + right_sum # WRONG: Missing node.val
Correct return:
return node.val + left_sum + right_sum # Include all three components
4. Edge Case: Single Node Tree
While the provided solution handles this correctly, it's worth noting that a single node (root with no children) should be counted if its value equals 0 (since the sum of its descendants is 0).
Test case to verify:
# Tree with single node having value 0 # 0 # Expected output: 1 (since sum of descendants = 0) # Tree with single node having value 5 # 5 # Expected output: 0 (since sum of descendants = 0, not 5)
The solution correctly handles this because leaf nodes get left_sum = 0
and right_sum = 0
from their null children, so the comparison 0 + 0 == node.val
works as expected.
How does merge sort divide the problem into subproblems?
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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!