expertfunda logo Top Interview Questions Binary Tree Binary Search Tree Graph

Count Complete Tree Nodes

Count Complete Tree Nodes Leetcode 222. Count Complete Tree Nodes

Count Complete Tree Nodes

Given the root of a complete binary tree, return the number of the nodes in the tree. According to Wikipedia, every level, except possibly the last, is completely filled in a complete binary tree, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h. Design an algorithm that runs in less than O(n) time complexity.

Approach 1:


class Solution {
    public int countNodes(TreeNode root) {
        return root != null ? 1 + countNodes(root.right) + countNodes(root.left) : 0;
    }
}
                

Time Complexity: O(n) and Space Complexity: O(n)


The problem Count Complete Tree Nodes requires counting the number of nodes in a complete binary tree. A complete binary tree is a binary tree in which every level, except possibly the last, is completely filled, and all nodes are as far left as possible.

Problem Statement

Given the root of a complete binary tree, you need to return the total number of nodes in the tree.

Approach to Solve the Problem

A complete binary tree is defined as a binary tree where all levels except possibly the last are completely filled. The nodes in the last level are as left as possible. To count the nodes in such a tree efficiently, we can take advantage of its special properties.

A naive approach would be to perform a depth-first search (DFS) or breadth-first search (BFS) traversal to count all nodes, but that would be inefficient. A better approach uses the structure of the complete binary tree to reduce the time complexity.

Optimal Approach:

The most efficient way to solve this problem is to use the fact that a complete binary tree is almost like a perfect binary tree except possibly at the last level. To count the nodes, we can perform a binary search-like approach to calculate the height of the tree and count nodes more effectively.

  • First, calculate the height of the leftmost path from the root to the bottom (this will give us the height of the tree).
  • Then, recursively check the number of nodes at each level by performing a binary search-like traversal from the root to the last level.
  • If the tree is a perfect binary tree, we can directly calculate the number of nodes as 2height - 1. Otherwise, count nodes recursively in the left and right subtrees and combine them.

Implementation

Here is the Python implementation:


class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def countNodes(self, root):
        if not root:
            return 0
        
        def treeHeight(node):
            height = 0
            while node:
                height += 1
                node = node.left
            return height
        
        def countNodesRecursive(node, height):
            if not node:
                return 0
            left_height = treeHeight(node.left)
            right_height = treeHeight(node.right)
            
            if left_height == right_height:
                return (1 << left_height) + countNodesRecursive(node.right, height - 1)
            else:
                return (1 << right_height) + countNodesRecursive(node.left, height - 1)
        
        height = treeHeight(root)
        return countNodesRecursive(root, height)
        

Here is the Java implementation:


public class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        int height = getHeight(root);
        
        if (height == 0) {
            return 0;
        }
        
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);
        
        if (leftHeight == rightHeight) {
            return (1 << leftHeight) + countNodes(root.right);
        } else {
            return (1 << rightHeight) + countNodes(root.left);
        }
    }
    
    private int getHeight(TreeNode node) {
        int height = 0;
        while (node != null) {
            height++;
            node = node.left;
        }
        return height;
    }
}
        

Example

Consider the following complete binary tree:

            Tree Structure:
                1
               / \
              2   3
             / \   /
            4   5 6
        

In this tree, the number of nodes is 6. The complete tree property is maintained, with all levels filled except the last one, which is as left as possible.

Another example:

            Tree Structure:
                1
               / \
              2   3
             / \
            4   5
        

In this case, the number of nodes is 5. The tree is complete, with all levels filled except the last one, which has two nodes positioned leftmost.

Complexity Analysis

Time Complexity: O(log2 n), where n is the number of nodes in the binary tree. The height of the tree is logarithmic in the number of nodes, and at each level, we perform a constant-time operation to count the nodes.

Space Complexity: O(log n), due to the recursive calls, as we are using the height of the tree as a parameter in the recursive function.

Applications

  • Helpful in determining the number of nodes in a complete binary tree where direct counting would be inefficient.
  • Efficient in solving problems related to binary heaps and trees used in priority queues.
  • Can be applied in scenarios requiring balanced binary trees for optimal search and access times.

Conclusion

The Count Complete Tree Nodes problem leverages the structure of a complete binary tree to count nodes efficiently. By using the height of the tree and performing a recursive traversal, we can calculate the number of nodes without needing to traverse every single node. This method significantly reduces the time complexity when compared to a straightforward depth-first or breadth-first search approach.

This problem is an excellent example of using tree properties to optimize operations, which is a valuable skill in algorithm design and problem-solving.