A breadth-first search solution for computing the average value of nodes at each level of a binary tree.
Problem Restatement
We are given the root of a binary tree.
We need to return an array where each element is the average value of all nodes on one level of the tree.
The first value is the average of the root level.
The second value is the average of the next level.
The result continues from top to bottom.
Answers within 10^-5 of the actual answer are accepted. The tree has at least one node, and node values may be large signed integers.
Input and Output
| Item | Meaning |
|---|---|
| Input | root, the root of a binary tree |
| Output | A list of floating-point averages |
| Traversal order | Top level to bottom level |
| Precision | Answer is accepted within 10^-5 |
Example function shape:
def averageOfLevels(root: Optional[TreeNode]) -> list[float]:
...Examples
Example 1:
root = [3, 9, 20, None, None, 15, 7]The tree is:
3
/ \
9 20
/ \
15 7Level averages:
| Level | Values | Average |
|---|---|---|
| 0 | [3] | 3.0 |
| 1 | [9, 20] | 14.5 |
| 2 | [15, 7] | 11.0 |
Output:
[3.0, 14.5, 11.0]Example 2:
root = [3, 9, 20, 15, 7]The output is also:
[3.0, 14.5, 11.0]First Thought: Traverse the Whole Tree
We need to visit every node because every value contributes to exactly one level average.
The question is how to group nodes by level.
A depth-first search can work if we store:
level_sum[level]
level_count[level]But breadth-first search fits the problem more directly because it naturally processes the tree one level at a time.
Key Insight
Use a queue.
At the start of each loop, the queue contains exactly the nodes of the current level.
So we can:
- Read the current queue size.
- Pop exactly that many nodes.
- Sum their values.
- Add their children to the queue.
- Divide the sum by the number of nodes on that level.
This gives one average per loop iteration.
Algorithm
- Create a queue containing the root node.
- Create an empty result list.
- While the queue is not empty:
- Store
level_size = len(queue). - Set
level_sum = 0. - Repeat
level_sizetimes:- Pop one node.
- Add its value to
level_sum. - Push its left child if it exists.
- Push its right child if it exists.
- Append
level_sum / level_sizeto the result.
- Store
- Return the result.
Implementation
from collections import deque
from typing import Optional
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def averageOfLevels(self, root: Optional[TreeNode]) -> list[float]:
queue = deque([root])
answer = []
while queue:
level_size = len(queue)
level_sum = 0
for _ in range(level_size):
node = queue.popleft()
level_sum += node.val
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
answer.append(level_sum / level_size)
return answerCode Explanation
We initialize the queue with the root:
queue = deque([root])Since the problem guarantees at least one node, root is valid.
At the beginning of each loop:
level_size = len(queue)This captures the number of nodes currently on this level.
Then we process exactly that many nodes:
for _ in range(level_size):This is important. Children added during this loop belong to the next level, so they must not be included in the current level’s average.
Each node contributes to the current sum:
level_sum += node.valThen we push its children:
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)After the level is complete, we compute the average:
answer.append(level_sum / level_size)Python / returns a floating-point result, which matches the required output type.
Correctness
At the start of the first loop, the queue contains only the root, which is exactly level 0.
Assume that at the start of some loop, the queue contains exactly all nodes on level d, and no other nodes.
The algorithm stores the queue length as level_size, then pops exactly level_size nodes. Therefore, it processes every node on level d exactly once.
For each processed node, the algorithm adds its children to the queue. All children of level d nodes are exactly the nodes on level d + 1. Since the loop only processes the original level_size nodes, these children remain in the queue for the next iteration.
Thus, by induction, every loop processes exactly one level.
During each loop, level_sum is the sum of all node values on that level, and level_size is the number of nodes on that level. Therefore, level_sum / level_size is exactly the average for that level.
The algorithm appends these averages from top to bottom, so the returned list is correct.
Complexity
Let n be the number of nodes in the tree.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each node is visited once |
| Space | O(w) | The queue stores at most one level of nodes |
Here, w is the maximum width of the tree.
In the worst case, w can be O(n).
Alternative Solution: DFS With Sums and Counts
We can also use depth-first search and store the sum and count for each depth.
from typing import Optional
class Solution:
def averageOfLevels(self, root: Optional[TreeNode]) -> list[float]:
sums = []
counts = []
def dfs(node: Optional[TreeNode], depth: int) -> None:
if node is None:
return
if depth == len(sums):
sums.append(0)
counts.append(0)
sums[depth] += node.val
counts[depth] += 1
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return [sums[i] / counts[i] for i in range(len(sums))]This also visits every node once.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each node is visited once |
| Space | O(h + L) | Recursion stack plus level arrays |
Here, h is the tree height, and L is the number of levels.
Testing
def run_tests():
s = Solution()
root = TreeNode(
3,
TreeNode(9),
TreeNode(20, TreeNode(15), TreeNode(7)),
)
assert s.averageOfLevels(root) == [3.0, 14.5, 11.0]
root = TreeNode(1)
assert s.averageOfLevels(root) == [1.0]
root = TreeNode(
-1,
TreeNode(-2),
TreeNode(-3),
)
assert s.averageOfLevels(root) == [-1.0, -2.5]
root = TreeNode(
5,
TreeNode(14, TreeNode(1), None),
None,
)
assert s.averageOfLevels(root) == [5.0, 14.0, 1.0]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Balanced sample tree | Standard level averages |
| Single node | Minimum tree |
| Negative values | Average can be negative |
| Skewed tree | Handles missing children |