Given the root
of a binary tree, return the number of uni-value
subtrees
.
A uni-value subtree means all nodes of the subtree have the same value.
Example 1:
Input: root = [5,1,5,5,5,null,5] Output: 4
Example 2:
Input: root = [] Output: 0
Example 3:
Input: root = [5,5,5,5,5,null,5] Output: 6
Constraints:
[0, 1000]
.-1000 <= Node.val <= 1000
Thought process:
Instinctively I take the main funciton countUnivalSubtrees as the one that should be recurred. So I divide the function into usual parts: one for base case which has no child nodes, automatically being a unival subtree, therefore counting as one; the rest part for recursive.
# 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 countUnivalSubtrees(self, root: Optional[TreeNode]) -> int:
if not root: return 0
if not root.left and not root.right: return 1
if not root.left:
if root.right.val == root.val:
return 1 + self.countUnivalSubtrees(root.right)
else:
return self.countUnivalSubtrees(root.right)
if not root.right:
if root.left.val == root.val:
return 1 + self.countUnivalSubtrees(root.left)
else:
return self.countUnivalSubtrees(root.left)
if root.right.val == root.val and root.left.val == root.val:
return 1 + self.countUnivalSubtrees(root.right) + self.countUnivalSubtrees(root.left)
else:
return self.countUnivalSubtrees(root.right) + self.countUnivalSubtrees(root.left)
However, it misses the scenerio that even when the node of interest has the same value as its child node, it doesn't necessarily mean its child node itself is also a unival subtree. In such case, the node of interest may violate the rule of being a unival subree. So we can't use this approach.
Just like the graph shown below. This approach takes the root node 1 as unival subtree bc both its children nodes have the same value as it, which should not be by definition.
Therefore, we have to separate the responsibilities: 1)identify whether the node of interest is a unival subtree by traversing all its children nods n comparing their value; 2) count the nodes that are satisfied such criteria.
# 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 countUnivalSubtrees(self, root: Optional[TreeNode]) -> int:
self.count = 0
def is_a_uni_value_subtree(node)-> bool:
if not node: return True
isLeftUniValue = is_a_uni_value_subtree(node.left)
isRightUniValue = is_a_uni_value_subtree(node.right)
if isLeftUniValue and isRightUniValue:
if node.left and node.left.val != node.val:
return False
if node.right and node.right.val != node.val:
return False
self.count += 1
return True
return False
is_a_uni_value_subtree(root)
return self.count
Explanation:
Here, the recursive calls are part of a conditional and
statement. The nature of the and
operator in Python (and many other languages) is short-circuiting. This means that if the first condition (is_a_uni_value_subtree(node.left)
) evaluates to False
, the second condition (is_a_uni_value_subtree(node.right)
) will not be evaluated at all.
This short-circuiting can lead to parts of the tree not being visited. Specifically, if any node's left subtree is not a uni-value subtree, the right subtree won't even be checked, leading to potential uni-value subtrees being missed.
So, in the second code, the recursive exploration of the tree can be prematurely halted due to the short-circuiting behavior of the and
operator, leading to a different (and likely incorrect) count of uni-value subtrees.
That's why we need to run both left child node and right child node first then put them into the conditional statement. So that we won't miss counting the right child node even the left child node is not a unival subtree.