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:
- Traverse the binary tree level by level (from top to bottom)
- For each level, calculate the average of all node values at that level
- 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)
- 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 is3.0
- Level 2 has two nodes:
9
and20
, so the average is(9 + 20) / 2 = 14.5
- Level 3 has two nodes:
15
and7
, 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:
- Track the depth/level of each node as you traverse
- Maintain separate sums and counts for each level
- 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.
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:
- Process all nodes at the current level together
- Calculate their sum and count in one go
- 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:
-
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
- Create a deque (double-ended queue)
-
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
- Record the current queue size
- While the queue is not empty:
-
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
- If
- Dequeue a node from the front:
- Loop exactly
-
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
- After processing all
-
Return the result:
- After the queue becomes empty (all levels processed), return
ans
- After the queue becomes empty (all levels processed), return
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 (wheren
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 EvaluatorExample 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]
- Dequeue node 5:
- 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]
- Dequeue node 3:
- 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
[]
- Dequeue node 2:
- 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:
- 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 spaceO(n)
. - Output array: The
ans
list stores one average value per level. In the worst case (a skewed tree), there could ben
levels, but typically there areO(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++ ordouble
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.
What's the relationship between a tree and a graph?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!