1448. Count Good Nodes In Binary Tree - Explanation
Description
Within a binary tree, a node x is considered good if the path from the root of the tree to the node x contains no nodes with a value greater than the value of node x
Given the root of a binary tree root, return the number of good nodes within the tree.
Example 1:
Input: root = [2,1,1,3,null,1,5]
Output: 3Example 2:
Input: root = [1,2,-1,3,4]
Output: 4Constraints:
1 <= number of nodes in the tree <= 100-100 <= Node.val <= 100
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n) time andO(n) space, where n is the number of nodes in the given tree.
Hint 1
A brute force solution would involve considering every node and checking if the path from the root to that node is valid, resulting in an O(n^2) time complexity. Can you think of a better approach?
Hint 2
We can use the Depth First Search (DFS) algorithm to traverse the tree. But can you think of a way to determine if the current node is a good node in a single traversal? Maybe we need to track a value while traversing the tree.
Hint 3
While traversing the tree, we should track the maximum value along the current path. This allows us to determine whether the nodes we encounter are good. We can use a global variable to count the number of good nodes.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Trees - Understanding tree node structures with left and right children
- Depth First Search (DFS) - Traversing trees recursively while passing state (max value) down each path
- Breadth First Search (BFS) - Level-order traversal using a queue with associated path information
1. Depth First Search
Intuition
A node is “good” if on the path from the root to that node, no earlier node has a value greater than it.
So while traversing the tree, we just need to carry the maximum value seen so far on the current path.
If the current node’s value ≥ that maximum → it is a good node.
Algorithm
- Start DFS from the root and store the root's value as the current
maxSoFar. - At each node:
- If
node.val >= maxSoFar, count it as a good node. - Update
maxSoFar = max(maxSoFar, node.val).
- If
- Recursively explore:
- Left child with updated
maxSoFar - Right child with updated
maxSoFar
- Left child with updated
- Sum the counts from left and right and return the total.
# 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 goodNodes(self, root: TreeNode) -> int:
def dfs(node, maxVal):
if not node:
return 0
res = 1 if node.val >= maxVal else 0
maxVal = max(maxVal, node.val)
res += dfs(node.left, maxVal)
res += dfs(node.right, maxVal)
return res
return dfs(root, root.val)Time & Space Complexity
- Time complexity:
- Space complexity:
2. Breadth First Search
Intuition
A node is “good” if along the path from the root to that node, no earlier node has a value greater than it.
Using BFS, we can traverse level by level while carrying the maximum value seen so far for each path.
Whenever we visit a node, we compare its value with that max — if it's greater or equal, this node is good.
Each child inherits the updated maximum of its own path.
Algorithm
- Use a queue that stores pairs:
(node, maxSoFarOnPath). - Start by pushing
(root, -infinity)into the queue. - While the queue is not empty:
- Pop one
(node, maxVal). - If
node.val >= maxVal, increase the good-node count. - For each child:
- Push
(child, max(maxVal, node.val))into the queue.
- Push
- Pop one
- Return the total count.
# 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 goodNodes(self, root: TreeNode) -> int:
res = 0
q = deque()
q.append((root,-float('inf')))
while q:
node,maxval = q.popleft()
if node.val >= maxval:
res += 1
if node.left:
q.append((node.left,max(maxval,node.val)))
if node.right:
q.append((node.right,max(maxval,node.val)))
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Using Strictly Greater Than Instead of Greater Than or Equal
A node is "good" if its value is greater than OR EQUAL to all ancestors. Using node.val > maxVal instead of node.val >= maxVal causes the root and equal-valued paths to be missed.
Initializing maxVal Too High
Starting with maxVal = 0 or maxVal = root.val can cause issues with negative values. Initialize with negative infinity or the root's value to correctly count the root as a good node.
# Wrong: misses root if root.val < 0
dfs(root, 0)
# Correct: root is always good
dfs(root, float('-inf'))Sharing maxVal Across Sibling Subtrees
The maximum value must be tracked per-path, not globally. Updating a shared variable instead of passing the new max to each recursive call causes incorrect comparisons across different branches.
Sign in to join the discussion