Facebook Pixel

637. Average of Levels in Binary Tree

Problem Description

You are given the root of a binary tree. Your task is to calculate the average value of all nodes at each level of the tree and return these averages as an array.

The problem asks you to:

  1. Traverse the binary tree level by level (from top to bottom)
  2. For each level, calculate the average of all node values at that level
  3. Store these averages in an array in order (first element is the average of level 1, second element is the average of level 2, and so on)
  4. Return this array of averages

For example, if you have a binary tree like:

      3
     / \
    9   20
       /  \
      15   7
  • Level 1 has one node: 3, so the average is 3.0
  • Level 2 has two nodes: 9 and 20, so the average is (9 + 20) / 2 = 14.5
  • Level 3 has two nodes: 15 and 7, so the average is (15 + 7) / 2 = 11.0

The output would be [3.0, 14.5, 11.0]

Note that the answers are considered correct if they are within 10^-5 of the actual answer, which accounts for floating-point precision issues.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure.

DFS

  • Following the flowchart, when we have a tree structure, the natural conclusion is to use DFS (Depth-First Search).

Conclusion: The flowchart suggests using a DFS approach for traversing the binary tree.

However, it's worth noting that while DFS is a valid approach for this problem, the solution provided actually uses BFS (Breadth-First Search) with a queue. This is because BFS naturally processes nodes level by level, which aligns perfectly with the problem's requirement to calculate averages for each level.

With DFS, you would need to:

  1. Track the depth/level of each node as you traverse
  2. Maintain separate sums and counts for each level
  3. Calculate averages after the complete traversal

The BFS approach in the solution is more intuitive for level-based problems as it processes all nodes at the same level together, making it easier to calculate the average for each level in one go.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find the average of nodes at each level, the key insight is that we need to process the tree level by level. While the flowchart suggests DFS for tree problems, this particular problem is actually better suited for BFS (Breadth-First Search).

Why? Because BFS naturally visits all nodes at the same depth before moving to the next level. This makes it perfect for level-order operations. With BFS, we can:

  1. Process all nodes at the current level together
  2. Calculate their sum and count in one go
  3. Compute the average immediately before moving to the next level

The approach uses a queue to maintain nodes to be processed. We start with the root node in the queue. For each level:

  • We record how many nodes are at this level (that's the current queue size)
  • We process exactly that many nodes from the queue
  • As we process each node, we add its value to our sum and enqueue its children
  • After processing all nodes at this level, we calculate the average as sum / count

This way, we naturally separate nodes by their levels without needing to track depth explicitly. The queue acts as a boundary between levels - when we finish processing n nodes (where n is the queue size at the start of the level), we know we've completed one level and the queue now contains exactly the nodes of the next level.

For example, with a tree like [3, 9, 20, null, null, 15, 7]:

  • Initially, queue has [3] - process it, add children, queue becomes [9, 20]
  • Queue has 2 nodes [9, 20] - process both, add their children, queue becomes [15, 7]
  • Queue has 2 nodes [15, 7] - process both, no more children, queue becomes empty

This level-by-level processing makes calculating averages straightforward and efficient.

Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.

Solution Approach

The solution implements BFS (Breadth-First Search) to traverse the binary tree level by level and calculate the average value of nodes at each level.

Implementation Details:

  1. Initialize the queue and result list:

    • Create a deque (double-ended queue) q with the root node
    • Create an empty list ans to store the average values
  2. Process each level:

    • While the queue is not empty:
      • Record the current queue size n - this represents the number of nodes at the current level
      • Initialize a sum variable s to 0
  3. Process all nodes at the current level:

    • Loop exactly n times (the number of nodes at this level):
      • Dequeue a node from the front: root = q.popleft()
      • Add the node's value to the sum: s += root.val
      • Enqueue the node's children (if they exist):
        • If root.left exists, append it to the queue
        • If root.right exists, append it to the queue
  4. Calculate and store the average:

    • After processing all n nodes at the current level
    • Calculate the average: s / n
    • Append this average to the answer list
  5. Return the result:

    • After the queue becomes empty (all levels processed), return ans

Why this works:

  • The queue maintains a clear separation between levels
  • When we start processing a level, the queue contains exactly the nodes of that level
  • As we process these nodes, we add their children (next level nodes) to the back of the queue
  • By processing exactly n nodes (where n is the initial queue size), we ensure we only process nodes from the current level before moving to the next

Time Complexity: O(n) where n is the total number of nodes, as we visit each node exactly once.

Space Complexity: O(w) where w is the maximum width of the tree, which represents the maximum number of nodes at any level stored in the queue at once.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Consider this binary tree:

      5
     / \
    3   8
   /   /
  2   6

Initial Setup:

  • Queue q = [5] (contains root)
  • Result list ans = []

Level 1 Processing:

  • Queue size n = 1 (one node at this level)
  • Initialize sum s = 0
  • Process 1 node:
    • Dequeue node 5: s = 0 + 5 = 5
    • Enqueue children: 3 and 8
    • Queue becomes [3, 8]
  • Calculate average: 5 / 1 = 5.0
  • Update result: ans = [5.0]

Level 2 Processing:

  • Queue size n = 2 (two nodes at this level)
  • Initialize sum s = 0
  • Process 2 nodes:
    • Dequeue node 3: s = 0 + 3 = 3
      • Enqueue child: 2
      • Queue becomes [8, 2]
    • Dequeue node 8: s = 3 + 8 = 11
      • Enqueue child: 6
      • Queue becomes [2, 6]
  • Calculate average: 11 / 2 = 5.5
  • Update result: ans = [5.0, 5.5]

Level 3 Processing:

  • Queue size n = 2 (two nodes at this level)
  • Initialize sum s = 0
  • Process 2 nodes:
    • Dequeue node 2: s = 0 + 2 = 2
      • No children to enqueue
      • Queue becomes [6]
    • Dequeue node 6: s = 2 + 6 = 8
      • No children to enqueue
      • Queue becomes []
  • Calculate average: 8 / 2 = 4.0
  • Update result: ans = [5.0, 5.5, 4.0]

Final Result: Queue is empty, return [5.0, 5.5, 4.0]

The key insight is that by processing exactly n nodes (where n is the queue size at the start of each level), we ensure we handle all nodes at the current level before moving to the next, making the average calculation straightforward.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8from collections import deque
9from typing import Optional, List
10
11class Solution:
12    def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
13        """
14        Calculate the average value of nodes at each level in a binary tree.
15      
16        Uses BFS (Breadth-First Search) to traverse the tree level by level.
17      
18        Args:
19            root: The root node of the binary tree
20          
21        Returns:
22            List of floats representing the average value at each level
23        """
24        # Initialize queue with root node for BFS traversal
25        queue = deque([root])
26      
27        # Store the average values for each level
28        result = []
29      
30        # Process each level of the tree
31        while queue:
32            # Get the number of nodes at current level
33            level_sum = 0
34            level_size = len(queue)
35          
36            # Process all nodes at the current level
37            for _ in range(level_size):
38                # Remove and process the front node
39                current_node = queue.popleft()
40                level_sum += current_node.val
41              
42                # Add children to queue for next level processing
43                if current_node.left:
44                    queue.append(current_node.left)
45                if current_node.right:
46                    queue.append(current_node.right)
47          
48            # Calculate and store the average for this level
49            level_average = level_sum / level_size
50            result.append(level_average)
51      
52        return result
53
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    /**
18     * Calculates the average value of nodes at each level of a binary tree.
19     * Uses BFS (Breadth-First Search) to traverse the tree level by level.
20     * 
21     * @param root The root node of the binary tree
22     * @return A list containing the average values for each level
23     */
24    public List<Double> averageOfLevels(TreeNode root) {
25        // List to store the average value of each level
26        List<Double> averagesList = new ArrayList<>();
27      
28        // Queue for BFS traversal
29        Deque<TreeNode> queue = new ArrayDeque<>();
30      
31        // Start BFS from the root node
32        queue.offer(root);
33      
34        // Process each level of the tree
35        while (!queue.isEmpty()) {
36            // Get the number of nodes at the current level
37            int levelSize = queue.size();
38          
39            // Use long to prevent integer overflow when summing large values
40            long levelSum = 0;
41          
42            // Process all nodes at the current level
43            for (int i = 0; i < levelSize; i++) {
44                // Remove and process the next node from the queue
45                TreeNode currentNode = queue.pollFirst();
46              
47                // Add current node's value to the level sum
48                levelSum += currentNode.val;
49              
50                // Add left child to queue for next level processing
51                if (currentNode.left != null) {
52                    queue.offer(currentNode.left);
53                }
54              
55                // Add right child to queue for next level processing
56                if (currentNode.right != null) {
57                    queue.offer(currentNode.right);
58                }
59            }
60          
61            // Calculate and store the average for this level
62            // Convert to double by multiplying by 1.0
63            averagesList.add(levelSum * 1.0 / levelSize);
64        }
65      
66        return averagesList;
67    }
68}
69
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    /**
15     * Calculate the average value of nodes at each level in a binary tree
16     * @param root - The root node of the binary tree
17     * @return A vector containing the average values for each level
18     */
19    vector<double> averageOfLevels(TreeNode* root) {
20        // Initialize queue for BFS traversal with the root node
21        queue<TreeNode*> nodeQueue;
22        nodeQueue.push(root);
23      
24        // Vector to store the average value of each level
25        vector<double> averages;
26      
27        // Process tree level by level using BFS
28        while (!nodeQueue.empty()) {
29            // Get the number of nodes at the current level
30            int levelSize = nodeQueue.size();
31          
32            // Use long long to prevent overflow when summing node values
33            long long levelSum = 0;
34          
35            // Process all nodes at the current level
36            for (int i = 0; i < levelSize; ++i) {
37                // Get and remove the front node from queue
38                TreeNode* currentNode = nodeQueue.front();
39                nodeQueue.pop();
40              
41                // Add current node's value to the level sum
42                levelSum += currentNode->val;
43              
44                // Add left child to queue if it exists
45                if (currentNode->left) {
46                    nodeQueue.push(currentNode->left);
47                }
48              
49                // Add right child to queue if it exists
50                if (currentNode->right) {
51                    nodeQueue.push(currentNode->right);
52                }
53            }
54          
55            // Calculate and store the average for this level
56            // Cast to double to ensure floating-point division
57            double levelAverage = static_cast<double>(levelSum) / levelSize;
58            averages.push_back(levelAverage);
59        }
60      
61        return averages;
62    }
63};
64
1/**
2 * Definition for a binary tree node.
3 */
4interface TreeNode {
5    val: number;
6    left: TreeNode | null;
7    right: TreeNode | null;
8}
9
10/**
11 * Calculates the average value of nodes at each level of a binary tree
12 * @param root - The root node of the binary tree
13 * @returns An array containing the average values for each level
14 */
15function averageOfLevels(root: TreeNode): number[] {
16    // Initialize queue with root node for level-order traversal
17    const queue: TreeNode[] = [root];
18  
19    // Array to store the average values for each level
20    const averages: number[] = [];
21  
22    // Process each level of the tree
23    while (queue.length > 0) {
24        // Store the number of nodes at current level
25        const levelSize: number = queue.length;
26      
27        // Array to store nodes for the next level
28        const nextLevelNodes: TreeNode[] = [];
29      
30        // Sum of all node values at current level
31        let levelSum: number = 0;
32      
33        // Process all nodes at current level
34        for (const node of queue) {
35            // Add current node's value to level sum
36            levelSum += node.val;
37          
38            // Add left child to next level if it exists
39            if (node.left) {
40                nextLevelNodes.push(node.left);
41            }
42          
43            // Add right child to next level if it exists
44            if (node.right) {
45                nextLevelNodes.push(node.right);
46            }
47        }
48      
49        // Calculate and store the average for current level
50        averages.push(levelSum / levelSize);
51      
52        // Replace current level nodes with next level nodes
53        queue.splice(0, queue.length, ...nextLevelNodes);
54    }
55  
56    return averages;
57}
58

Time and Space Complexity

Time Complexity: O(n)

The algorithm uses BFS (Breadth-First Search) to traverse the binary tree level by level. Each node in the tree is visited exactly once - it's added to the queue once and processed once when it's dequeued. For each node, we perform constant time operations: accessing its value, checking for left/right children, and adding children to the queue. Since we visit all n nodes exactly once with O(1) operations per node, the total time complexity is O(n).

Space Complexity: O(n)

The space complexity is determined by two factors:

  1. Queue space: In the worst case, the queue needs to store all nodes at the widest level of the tree. For a complete binary tree, the last level can contain up to n/2 nodes, making the queue space O(n).
  2. Output array: The ans list stores one average value per level. In the worst case (a skewed tree), there could be n levels, but typically there are O(log n) levels. However, the queue dominates the space complexity.

Therefore, the overall space complexity is O(n), where n is the number of nodes in the binary tree.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Integer Division Instead of Float Division

One common mistake is accidentally using integer division when calculating the average, especially in languages that distinguish between integer and float division (like Python 2 or C++).

Incorrect approach:

# In Python 2 or when using // operator
level_average = level_sum // level_size  # This would truncate the decimal

Solution: Ensure you're using float division by:

  • Using / operator in Python 3 (which always returns float)
  • Explicitly casting to float in other languages
  • Using level_sum * 1.0 / level_size to force float arithmetic

2. Not Handling Empty Tree Edge Case

The code assumes the root exists, but if root is None, the code will fail when trying to access root.val.

Solution: Add an initial check:

def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
    if not root:
        return []
  
    queue = deque([root])
    # ... rest of the code

3. Integer Overflow for Large Values

When summing node values at a level, if the values are very large or there are many nodes, the sum might overflow in languages with fixed integer sizes.

Solution:

  • Use appropriate data types (like long long in C++ or double for the sum)
  • In Python, this isn't an issue due to arbitrary precision integers
  • Consider calculating running average instead of sum: avg = (avg * processed_count + current_val) / (processed_count + 1)

4. Modifying Queue Size During Iteration

A subtle bug can occur if you accidentally check len(queue) inside the loop instead of using the saved level_size:

Incorrect approach:

while queue:
    level_sum = 0
    # DON'T do this - queue size changes as we add children
    for _ in range(len(queue)):  # Wrong if checked here!
        current_node = queue.popleft()
        # ... add children to queue

Why it fails: As we process nodes and add their children, the queue size changes, causing us to process nodes from multiple levels together.

Solution: Always save the initial queue size before the loop starts, as shown in the correct implementation.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More