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

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

  1. Initialize a global counter to track uni-value subtrees.
  2. Define a recursive DFS function that returns true if the subtree rooted at the current node is a uni-value subtree.
  3. Base case: if the node is null, return true.
  4. Recursively check the left and right subtrees.
  5. 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.
  6. Otherwise, return false (one of the subtrees is not uni-value).
  7. 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.count

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn 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

  1. Define a recursive DFS function that takes the current node and a count array (or reference) as parameters.
  2. Base case: if the node is null, return true.
  3. Recursively check the left and right subtrees, passing the count array.
  4. If both subtrees are uni-value and the current node's value matches its children's values (when they exist):
    • Increment count[0] and return true.
  5. Otherwise, return false.
  6. Create a count array initialized to 0, call DFS on the root, and return count[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: O(n)O(n)
  • Space complexity: O(n)O(n)

Where nn 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 False

Incrementing 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