150NeetCode 150

55.Count Good Nodes in Binary Tree

MediumTrees ยท 10/15

Problem

Given the root of a binary tree, a node X is called good if, on the path from the root down to X, there is no node with a value greater than X's value.

Return the number of good nodes. The root is always good, and nodes equal to the path maximum also count as good.

Examples

Input:  root = [3,1,4,3,null,1,5]
Output: 4
Explanation: Good nodes are 3 (root), 4 (path 3 -> 4), 5 (path 3 -> 4 -> 5)
             and the lower 3 (path 3 -> 1 -> 3). Node 1 (under 3) and node 1 (under 4) are not good.
Input:  root = [3,3,null,4,2]
Output: 3
Explanation: 3, 3 and 4 are good; 2 is not because 3 > 2 on its path.
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].

๐Ÿ’ก Key Insight

Whether a node is good depends only on the maximum value on its root path, not on the whole path. So pass that single number down during a traversal: a node is good iff node.val >= maxSoFar, and its children receive max(maxSoFar, node.val). One pass, O(n).

Brute Force (Store the Whole Path)

TimeO(n ยท h)SpaceO(h)

Intuition

Literally follow the definition: keep the full root-to-node path in a list during DFS, and for each node scan the path to see if any ancestor is larger.

Algorithm

  1. DFS with a path list.
  2. At each node, scan path; if no value exceeds node.val, count it.
  3. Push node.val onto path, recurse into both children, then remove it (backtrack).

Visualization

root = [3,1,4,3,null,1,5]

node 3, path []        -> good
node 1, path [3]       -> 3 > 1, not good
node 3, path [3,1]     -> no value > 3, good
node 4, path [3]       -> good
node 1, path [3,4]     -> not good
node 5, path [3,4]     -> good
total = 4

Code

java
import java.util.ArrayList;
import java.util.List;

/**
 * Definition for a binary tree node.
 * public 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, new ArrayList<>());
    }

    private int dfs(TreeNode node, List<Integer> path) {
        if (node == null) {
            return 0;
        }
        boolean good = true;
        for (int ancestor : path) {
            if (ancestor > node.val) {
                good = false;
                break;
            }
        }
        path.add(node.val);
        int count = (good ? 1 : 0) + dfs(node.left, path) + dfs(node.right, path);
        path.remove(path.size() - 1); // backtrack
        return count;
    }
}

Complexity

  • Time: O(n ยท h) โ€” each node scans a path of length up to h; O(nยฒ) for a skewed tree.
  • Space: O(h) โ€” the path list and recursion stack.

Complexity Comparison

Approach Time Space
Brute Force (Store the Whole Path) O(n ยท h) O(h)
BFS with (Node, Max) Pairs O(n) O(w)
DFS with Running Max O(n) O(h)

Common Pitfalls

  • Using > instead of >= โ€” a node equal to the path maximum is still good (the lower 3 in example 1).
  • Updating a shared/global max instead of passing it down; values from one branch then leak into the sibling branch.
  • Initialising the max to 0 โ€” values can be negative, so a negative root would be wrongly marked not good. Use root.val or Integer.MIN_VALUE.
  • With 10^5 nodes in a skewed tree, recursion depth is 10^5; the BFS version avoids any stack-depth concern.