Language Selection
Choose your preferred programming language
Showing: Python
Count Good Nodes in Binary Tree
Problem Statement
Given a binary tree root, a node X in the tree is named good if in the path from root to X there are no nodes with a value greater than X.
Return the number of good nodes in the binary tree.
Examples
Example 1:
Input: root = [3,1,4,3,null,1,5]
Output: 4
Explanation: Nodes in blue are good.
Root Node (3) is always a good node.
Node 4 -> (3,4) is the maximum value in the path
Node 5 -> (3,4,5) is the maximum value in the path
Node 3 -> (3,1,3) is the maximum value in the path
Example 2:
Input: root = [3,3,null,4,2]
Output: 3
Explanation: Node 2 -> (3, 3, 2) is not good, because "3" is higher than it.
Constraints
- The number of nodes in the binary tree is in the range
[1, 10^5] - Each node’s value is between
[-10^4, 10^4]
Approach 1: DFS with Path Maximum
Algorithm
Use depth-first search to traverse the tree while keeping track of the maximum value encountered in the current path from root.
Steps:
- Start DFS from root with initial maximum value as root’s value
- For each node, check if current node’s value is >= current maximum
- If yes, increment good nodes count and update maximum
- Recursively traverse left and right subtrees
- Return total count of good nodes
Implementation
Python:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def goodNodes(root):
"""
Count good nodes using DFS with path maximum tracking
Time: O(n)
Space: O(h) where h is height of tree
"""
def dfs(node, max_val):
if not node:
return 0
# Check if current node is good
count = 1 if node.val >= max_val else 0
# Update maximum value for children
new_max = max(max_val, node.val)
# Recursively count good nodes in subtrees
count += dfs(node.left, new_max)
count += dfs(node.right, new_max)
return count
return dfs(root, root.val)
Java:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
class Solution {
public int goodNodes(TreeNode root) {
return dfs(root, root.val);
}
private int dfs(TreeNode node, int maxVal) {
if (node == null) {
return 0;
}
// Check if current node is good
int count = (node.val >= maxVal) ? 1 : 0;
// Update maximum value for children
int newMax = Math.max(maxVal, node.val);
// Recursively count good nodes in subtrees
count += dfs(node.left, newMax);
count += dfs(node.right, newMax);
return count;
}
}
Complexity Analysis
- Time Complexity: O(n) where n is the number of nodes in the tree
- Space Complexity: O(h) where h is the height of the tree (recursion stack)
Key Insights
- Path Maximum Tracking: The key insight is to track the maximum value in the current path from root
- Root is Always Good: The root node is always considered good since it has no ancestors
- DFS Traversal: Any traversal order works, but DFS is most natural for path-based problems
- Recursive vs Iterative: Both approaches have the same time complexity but different space characteristics
Edge Cases
- Single node: Root is always good, return 1
- All decreasing values: All nodes are good
- All increasing values: Only root is good
- Negative values: Handle negative values correctly
- Deep tree: Ensure recursion doesn’t cause stack overflow
Test Cases
# Test cases
# Example 1: [3,1,4,3,null,1,5] -> 4
# Example 2: [3,3,null,4,2] -> 3
# Example 3: [1] -> 1
Follow-up Questions
- Path Sum: Find the sum of all good nodes
- Good Paths: Find all paths that contain only good nodes
- Minimum Good Nodes: Find the minimum number of nodes to make all paths good
- Good Subtree: Find the largest subtree where all nodes are good
Common Mistakes
- Incorrect comparison: Using
>instead of>=for good node check - Not updating maximum: Forgetting to update max value for children
- Root handling: Not properly handling the root node case
- Path tracking: Not correctly tracking the path maximum
Interview Tips
- Start with examples: Walk through the examples to understand the problem
- Clarify definition: Make sure you understand what makes a node “good”
- Think about traversal: Consider which traversal order makes sense
- Handle edge cases: Always consider single node and null cases
- Discuss optimization: Mention both recursive and iterative approaches