Start learning free

LESSON 425 OF 568 · BINARY TREES

Count Good Nodes in Binary Tree in C++: O(n) DFS

Practice O(n) Read the full explanation

Given the root of a binary tree, return the number of good nodes in the tree.

A node X is good when no node on the path from the root down to X has a value greater than the value of X.

A node whose value equals the largest value above it is still good, and the root is always good: nothing is above it.

The trees below are written in level order (top to bottom, left to right), with NULL for a missing child.

 

Example 1:

      3
     / \
    1   4
   /   / \
  3   1   5

Input: root = [3,1,4,3,NULL,1,5]
Output: 4
Explanation: The good nodes are the root 3, then 4 (path 3 → 4), 5 (path 3 → 4 → 5) and the 3 at the bottom left (path 3 → 1 → 3: nothing on it is greater than 3). The 1 under the root is not good: 3 is greater. Neither is the 1 under 4: 3 and 4 are greater.

Example 2:

      3
     /
    3
   / \
  4   2

Input: root = [3,3,NULL,4,2]
Output: 3
Explanation: The root 3, the 3 under it (an equal value is not greater) and the 4 are good. The 2 is not: the path 3 → 3 → 2 has greater values.

Example 3:

Input: root = [1]
Output: 1
Explanation: The root is always good.

 

Constraints:

  • The number of nodes in the tree is in the range [1, 105].
  • Each node value is between -104 and 104.
  • The tree is at most 800 levels deep.

Note:

Your algorithm should run in O(n) time, where n is the number of nodes, visiting each node once.

Walking back up the path from every node would take up to O(n · h) time for a tree h levels deep. Instead, carry what you need down the path as you go.

STEP 1 OF 9
    Ctrl + Enter to check

    C++ · BINARY TREES

    Count Good Nodes In Binary Tree in C++, explained

    Problem overview

    The method goodNodes receives the root of a binary tree with at least one node and returns how many of its nodes are good. A node X is good when no node on the path from the root down to X has a value greater than the value of X. A node that equals the largest value above it is still good, and the root is always good because nothing is above it. The lesson provides the TreeNode struct, with an int val and the pointers left and right.

    In the first example, [3,1,4,3,NULL,1,5], the good nodes are the root 3, the 4, the 5 (path 3, 4, 5) and the 3 at the bottom left (path 3, 1, 3: nothing on it is greater than 3). The 1 under the root is not good because 3 is greater, and neither is the 1 under the 4. The answer is 4. In the second example, [3,3,NULL,4,2], the root, the 3 under it (an equal value is not greater) and the 4 are good, but the 2 is not, so the answer is 3. A tree with a single node has exactly one good node.

    Approach: carry the maximum down the path

    You could test every node by walking back up its path, but the paths overlap heavily and the total cost can reach O(n · h) for a tree h levels deep. There is a simpler way: walk down once and carry the information you need.

    The only fact needed about the path above a node is the largest value on it. A node is good exactly when its value is at least that maximum, since one comparison covers the whole path. Going down, each node passes its children the larger of the incoming maximum and its own value, which is the maximum of the path that now includes it. Both children get the same number, because they share the path down to their parent, and nothing in the left branch is on the right branch's path.

    The first example, walked from the top, with the maximum seen above each node:

    node 3 maxSoFar=3 good
      node 1 maxSoFar=3 not good
        node 3 maxSoFar=3 good
      node 4 maxSoFar=3 good
        node 1 maxSoFar=4 not good
        node 5 maxSoFar=4 good

    Four lines say "good", so the answer is 4. The root starts with its own value as the maximum so far, which makes it good automatically.

    Step by step

    1. Delegate from goodNodes. Call a recursive helper, countGood, on root with root->val as the largest value so far, and return its answer. Starting from the root's own value lets the root pass its own test. Starting from 0 would be wrong when every value is negative.
    2. Write the helper's signature. countGood returns an int and takes a TreeNode* node and an int maxSoFar, the largest value on the path above node. It goes above goodNodes in the class.
    3. Handle the empty tree. If node is nullptr (!node), return 0. An empty tree has no good nodes, and this check stops the recursion past a leaf.
    4. Declare count as an int starting at 0. It will hold this node (if good) plus the good nodes below it.
    5. Test this node. If node->val >= maxSoFar, set count to 1. Equal counts as good: only a strictly greater value outdoes a node.
    6. Compute newMax, the larger of maxSoFar and node->val, with max. The children have this node on their path too, so the value they must reach may now be this node's own.
    7. Count the left subtree: count += countGood(node->left, newMax).
    8. Count the right subtree the same way with node->right.
    9. Return count.

    The solution in C++

    class Solution {
    public:
        int countGood(TreeNode* node, int maxSoFar) {
            if (!node) {
                return 0;
            }
            int count = 0;
            if (node->val >= maxSoFar) {
                count = 1;
            }
            int newMax = max(maxSoFar, node->val);
            count += countGood(node->left, newMax);
            count += countGood(node->right, newMax);
            return count;
        }
    
        int goodNodes(TreeNode* root) {
            return countGood(root, root->val);
        }
    };
    

    C++ details worth knowing:

    • Null pointers. !node is true for a null pointer. The base case has to come before any use of node->val, because dereferencing a null pointer is undefined behaviour.
    • Value parameter. maxSoFar is passed by value, so each call has its own copy. The left call can't change the number that the right call sees. That is why both children get the same newMax without any bookkeeping.
    • Reading the root's value. goodNodes uses root->val, which is safe because the constraints promise at least one node. With a possibly empty tree you would need to test root first.
    • Recursion depth. The tree is at most 800 levels deep, so the recursion fits easily on the call stack. A chain of 800 increasing values returns 800.

    Complexity

    Time: O(n). Every node is visited once and does a comparison, a max and two calls.

    Space: O(h). The recursion keeps one frame per level of the tree, so the extra memory is proportional to the height h.

    Edge cases and common mistakes

    • All values negative. For [-5, -7, -3, -9] the good nodes are the root -5 and the -3 (path -5, -3), so the answer is 2. Starting the maximum at 0 gives 0, because no negative value reaches it. Starting from the root's value, or from the smallest possible int, gives 2.
    • Equal values. A tree where every value is 2, such as [2, 2, 2, 2], has 4 good nodes. Using > instead of >= would count only the root.
    • A decreasing chain. For [5, 4, NULL, 3, NULL, 2] only the root is good: every later node has a bigger value above it.
    • An increasing chain. For [1, NULL, 2, NULL, 3, NULL, 4] every node is good, so the answer is 4.
    • Comparing with the parent only. A node bigger than its parent is not automatically good. In [5, 1, NULL, 3] the 3 is bigger than its parent 1, but the root 5 is above it, so the answer is 1.
    • Passing the wrong maximum to a child. If the children receive maxSoFar instead of newMax, a node's own value is forgotten. For [1, 3, NULL, 2] the 2 has the 3 above it and is not good, so the answer is 2, but that mistake counts it and returns 3.

    Testing

    • [3,1,4,3,NULL,1,5] gives 4.
    • [3,3,NULL,4,2] gives 3.
    • [1] gives 1.
    • [-5,-7,-3,-9] gives 2.
    • [2,2,2,2] gives 4.
    • [5,4,NULL,3,NULL,2] gives 1.
    • [1,NULL,2,NULL,3,NULL,4] gives 4.
    • [5,1,NULL,3] gives 1.
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free