Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    MediumTrees

    Count Good Nodes In Binary Tree

    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.

    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 starting from the root. Node 5 -> (3,4,5) is the maximum value in the path. Node 3 -> (3,1,3) is also good, since 3 is the maximum value seen so far.

    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.

    Example 3

    Input: root = [1]

    Output: 1

    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].
    View original on LeetCode ↗

    Whether a node is “good” depends only on the maximum value seen so far along the path from the root — that value can simply be carried down as an extra DFS argument, so each node is checked in O(1) without ever re-walking a path.

    Brute Force (re-walk each path)

    Time O(n^2)Space O(h)

    For every node, walk back up to the root (or, equivalently, track the full path down and re-scan it) to check whether any ancestor has a strictly greater value.

    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 count(node: TreeNode | None, path: list[int]) -> int:
    if node is None:
    return 0
    is_good = 1 if node.val >= max(path, default=node.val) else 0
    new_path = path + [node.val]
    return is_good + count(node.left, new_path) + count(node.right, new_path)
    return count(root, [])

    Complexity: max(path) re-scans the whole path-so-far at every node, and paths can be O(n) long → O(n^2) time worst case (a skewed tree). Space for the path list plus recursion stack → O(h).

    DFS Carrying Running Max

    OptimalTime O(n)Space O(h)

    Pass the maximum value seen so far as a single running argument instead of the whole path. A node is good exactly when its value is at least that running max; the max passed to its children is then max(running_max, node.val).

    class Solution:
    def goodNodes(self, root: TreeNode) -> int:
    def dfs(node: TreeNode | None, max_so_far: int) -> int:
    if node is None:
    return 0
    is_good = 1 if node.val >= max_so_far else 0
    new_max = max(max_so_far, node.val)
    return is_good + dfs(node.left, new_max) + dfs(node.right, new_max)
    return dfs(root, root.val)

    Watch the single number that each call carries down: a node is judged against the max on its own root path, and the child receives that max updated with the node’s own value.

    3
    1
    4
    3
    1
    5
    max 3
    max ?
    max ?
    max ?
    max ?
    max ?

    Decision at the current node

    node value3
    ≥
    max so far3
    →
    verdictgood
    →
    new max3
    good nodes1
    1 / 7
    currentcomparingresultdiscarded

    Start at root 3. Its incoming max is 3, so 3 >= 3 and it counts as good. Both children receive max_so_far = 3.

    Complexity: each node does O(1) work with the running max instead of rescanning a path → O(n) time. Space is the recursion stack (the running max is a single O(1) value passed along it) → O(h).

    More Trees practice