Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Trees - Understanding tree structure, nodes, and traversal patterns
- Depth First Search (DFS) - Recursive traversal of tree structures from leaves to root
- Post-order Traversal - Processing children before parent nodes to aggregate subtree information
1. Depth First Search
Intuition
A uni-value subtree is one where all nodes have the same value. We can use DFS to check each subtree from the bottom up. For a node to be the root of a uni-value subtree, both its children must be roots of uni-value subtrees, and the node's value must match its children's values (if they exist). Leaf nodes are always uni-value subtrees.
Algorithm
- Initialize a global counter to track uni-value subtrees.
- Define a recursive DFS function that returns
trueif the subtree rooted at the current node is a uni-value subtree. - Base case: if the node is
null, returntrue. - Recursively check the left and right subtrees.
- If both subtrees are uni-value:
- Check if the left child exists and has a different value; if so, return
false. - Check if the right child exists and has a different value; if so, return
false. - Increment the counter and return
true.
- Check if the left child exists and has a different value; if so, return
- Otherwise, return
false(one of the subtrees is not uni-value). - Call DFS on the root and return the counter.
class Solution:
def countUnivalSubtrees(self, root: Optional[TreeNode]) -> int:
self.count = 0
def dfs(node):
if node is None:
return True
isLeftUniValue = dfs(node.left)
isRightUniValue = dfs(node.right)
# If both the children form uni-value subtrees, we compare the value of
# chidrens node with the node value.
if isLeftUniValue and isRightUniValue:
if node.left and node.val != node.left.val:
return False
if node.right and node.val != node.right.val:
return False
self.count += 1
return True
# Else if any of the child does not form a uni-value subtree, the subtree
# rooted at node cannot be a uni-value subtree.
return False
dfs(root)
return self.countTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of nodes in the given binary tree
2. Depth First Search Without Using The Global Variable
Intuition
The previous solution uses a global or instance variable to track the count. We can avoid this by passing a mutable container (like an array or reference) through the recursion. This makes the function more self-contained and easier to test, while keeping the same logic.
Algorithm
- Define a recursive DFS function that takes the current node and a count array (or reference) as parameters.
- Base case: if the node is
null, returntrue. - Recursively check the left and right subtrees, passing the count array.
- If both subtrees are uni-value and the current node's value matches its children's values (when they exist):
- Increment
count[0]and returntrue.
- Increment
- Otherwise, return
false. - Create a count array initialized to
0, call DFS on the root, and returncount[0].
class Solution:
def countUnivalSubtrees(self, root: Optional[TreeNode]) -> int:
def dfs(node, count):
if node is None:
return True
isLeftUniValue = dfs(node.left, count)
isRightUniValue = dfs(node.right, count)
# If both the children form uni-value subtrees, we compare the value of
# chidrens node with the node value.
if isLeftUniValue and isRightUniValue:
if node.left and node.val != node.left.val:
return False
if node.right and node.val != node.right.val:
return False
count[0] += 1
return True
# Else if any of the child does not form a uni-value subtree, the subtree
# rooted at node cannot be a uni-value subtree.
return False
count = [0]
dfs(root, count)
return count[0]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of nodes in the given binary tree
Common Pitfalls
Short-Circuit Evaluation Breaking the Count
Using short-circuit evaluation with and can skip processing one subtree entirely, causing missed counts. Both subtrees must be fully traversed to count all uni-value subtrees, even if one returns false.
# Incorrect - if left is false, right subtree is never processed
if dfs(node.left) and dfs(node.right):
# ...
# Correct - process both subtrees before combining results
left = dfs(node.left)
right = dfs(node.right)
if left and right:
# ...Comparing Node Values Before Checking Null
Attempting to access a child's value without first checking if the child exists causes a null pointer exception. Always verify the child is not null before comparing values.
# Incorrect - crashes if node.left is None
if node.left.val != node.val:
return False
# Correct - check for null first
if node.left and node.left.val != node.val:
return FalseIncrementing Count for Non-Univalue Subtrees
A common mistake is incrementing the count before validating that the current node's value matches its children. The count should only increase after confirming all uni-value conditions are met.
# Incorrect - increments before checking value match
if left and right:
count += 1
if node.left and node.val != node.left.val:
return False
# Correct - increment only after all checks pass
if left and right:
if node.left and node.val != node.left.val:
return False
if node.right and node.val != node.right.val:
return False
count += 1
return True