1448. Count Good Nodes In Binary Tree - Explanation

Problem Link

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: 3

Example 2:

Input: root = [1,2,-1,3,4]

Output: 4

Constraints:

  • 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.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



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

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

  1. Start DFS from the root and store the root's value as the current maxSoFar.
  2. At each node:
    • If node.val >= maxSoFar, count it as a good node.
    • Update maxSoFar = max(maxSoFar, node.val).
  3. Recursively explore:
    • Left child with updated maxSoFar
    • Right child with updated maxSoFar
  4. 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: O(n)O(n)
  • Space complexity: O(n)O(n)

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

  1. Use a queue that stores pairs: (node, maxSoFarOnPath).
  2. Start by pushing (root, -infinity) into the queue.
  3. 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.
  4. 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 res

Time & Space Complexity

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

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.