958. Check Completeness of a Binary Tree
Problem Description
You need to determine if a given binary tree is a complete binary tree.
A complete binary tree has the following properties:
- Every level is completely filled with nodes, except possibly the last level
- The last level must be filled from left to right without any gaps
- If the last level is not full, all nodes must be positioned as far left as possible
For example:
- A tree with nodes arranged like
[1, 2, 3, 4, 5]
where nodes 4 and 5 are children of node 2 would be complete - A tree with nodes arranged like
[1, 2, 3, null, null, 4]
where node 4 is the left child of node 3 would be complete - A tree with nodes arranged like
[1, 2, 3, 4, null, null, 5]
would NOT be complete because there's a gap (node 2 has no right child while node 3 has a child)
The solution uses a level-order traversal (BFS) approach. It processes nodes level by level using a queue. When it encounters the first None
node, it stops adding nodes to the queue. After this point, if there are any non-None
nodes remaining in the queue, the tree is not complete. If all remaining nodes are None
, the tree is complete.
The key insight is that in a complete binary tree, once you encounter a None
node during level-order traversal, all subsequent nodes must also be None
. Any non-None
node after a None
indicates a gap in the tree structure, violating the complete binary tree property.
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 (specifically, a connected acyclic graph).
Is it a tree?
- Yes: The problem explicitly deals with a binary tree structure.
DFS?
- Not necessarily: While DFS could work, the problem requires checking level-by-level completeness. We need to verify that all levels are filled from left to right, which is more naturally suited to level-order traversal (BFS). The solution needs to detect gaps in the tree structure by processing nodes level by level.
Let's reconsider the path through the flowchart:
Since we need level-order traversal to check completeness (processing nodes level by level from left to right), and the flowchart paths from "tree" lead to DFS, we should note that for this specific tree problem, BFS is the optimal choice even though it's not the default tree traversal suggested by the flowchart.
The key insight is that checking for completeness requires:
- Processing nodes in level order (left to right)
- Detecting when we encounter the first
None
node - Verifying no non-
None
nodes appear after the firstNone
This pattern perfectly aligns with BFS traversal using a queue, where we can naturally process nodes level by level and detect gaps in the tree structure.
Conclusion: The flowchart would typically suggest DFS for tree problems, but the specific requirement of checking completeness (which involves level-order properties) makes BFS (Breadth-First Search) the ideal approach for this problem.
Intuition
The key insight for checking if a binary tree is complete comes from understanding what happens during a level-order traversal of a complete vs. incomplete tree.
In a complete binary tree, when we traverse level by level from left to right, all non-null nodes appear consecutively at the beginning, followed by all null nodes at the end. There's never a situation where we see a null node followed by a non-null node.
Think about it this way: imagine pouring water into a tree structure level by level, from left to right. In a complete tree, the water fills continuously without leaving any gaps. The moment we encounter an empty spot (null node), everything after it must also be empty.
This observation leads to a simple algorithm:
- Use BFS to traverse the tree level by level
- Add all nodes to the queue, including
None
children - When we encounter the first
None
node, stop the traversal - Check if any non-
None
nodes remain in the queue
If there are non-None
nodes after we've seen a None
, it means there's a gap in our tree structure - violating the complete binary tree property. For example, if node 2 has no right child but node 3 (at the same level) has children, we'd encounter None
from node 2's missing child, then later see node 3's children in the queue.
The beauty of this approach is that we don't need to track levels, positions, or do complex calculations. We simply rely on the fact that in a complete tree, once we see a "hole" (null node) during level-order traversal, everything else must be a hole too.
Learn more about Tree, Breadth-First Search and Binary Tree patterns.
Solution Approach
The implementation uses a BFS (Breadth-First Search) approach with a queue data structure to traverse the tree level by level.
Here's the step-by-step walkthrough of the algorithm:
-
Initialize the Queue: Start with a deque containing the root node.
q = deque([root])
-
Process Nodes Until First None: Use a while loop to process nodes from the queue:
while q: node = q.popleft() if node is None: break q.append(node.left) q.append(node.right)
- Remove the first node from the queue
- If it's
None
, immediately break out of the loop - If it's a valid node, add both its children to the queue (even if they are
None
)
-
Check Remaining Queue: After breaking from the loop (when we encounter the first
None
), check if all remaining elements in the queue areNone
:return all(node is None for node in q)
- If all remaining nodes are
None
, the tree is complete - If any non-
None
node exists, the tree is not complete
- If all remaining nodes are
The algorithm's correctness relies on a crucial property: in a complete binary tree's level-order traversal, all None
nodes must appear after all non-None
nodes. Any violation of this pattern indicates an incomplete tree.
Time Complexity: O(n)
where n is the number of nodes, as we visit each node at most once.
Space Complexity: O(w)
where w is the maximum width of the tree, which in the worst case (complete tree) is O(n/2)
= O(n)
for the last level.
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 the algorithm with a concrete example to see how it identifies whether a tree is complete.
Example Tree:
1 / \ 2 3 / \ 4 5
This tree should be complete. Let's trace through the algorithm:
Step 1: Initialize
- Queue:
[1]
Step 2: Process node 1
- Pop node 1 from queue
- Node 1 is not None, so add its children
- Queue:
[2, 3]
Step 3: Process node 2
- Pop node 2 from queue
- Node 2 is not None, so add its children
- Queue:
[3, 4, 5]
Step 4: Process node 3
- Pop node 3 from queue
- Node 3 is not None, so add its children (both None)
- Queue:
[4, 5, None, None]
Step 5: Process node 4
- Pop node 4 from queue
- Node 4 is not None, so add its children (both None)
- Queue:
[5, None, None, None, None]
Step 6: Process node 5
- Pop node 5 from queue
- Node 5 is not None, so add its children (both None)
- Queue:
[None, None, None, None, None, None]
Step 7: Encounter first None
- Pop first None from queue
- Break from the while loop
- Queue still contains:
[None, None, None, None, None]
Step 8: Check remaining queue
- All remaining elements are None
- Return
True
- the tree is complete!
Counter-example (Incomplete Tree):
1 / \ 2 3 \ \ 5 6
Following the same process:
- After processing nodes 1, 2, and 3, queue would be:
[None, 5, None, 6]
- When we pop the first None and break
- Queue contains:
[5, None, 6]
- Since there are non-None nodes (5 and 6) after we encountered a None, return
False
The key insight: the presence of nodes 5 and 6 after a None indicates gaps in the tree structure, violating the complete binary tree property.
Solution Implementation
1from collections import deque
2from typing import Optional
3
4# Definition for a binary tree node.
5class TreeNode:
6 def __init__(self, val: int = 0, left: Optional['TreeNode'] = None, right: Optional['TreeNode'] = None):
7 self.val = val
8 self.left = left
9 self.right = right
10
11class Solution:
12 def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
13 """
14 Check if a binary tree is a complete binary tree.
15
16 A complete binary tree is a binary tree in which all levels are completely filled
17 except possibly the last level, and the last level is filled left to right.
18
19 Args:
20 root: The root node of the binary tree
21
22 Returns:
23 True if the tree is complete, False otherwise
24 """
25 # Initialize a queue for level-order traversal
26 queue = deque([root])
27
28 # Perform level-order traversal until we encounter the first None node
29 while queue:
30 current_node = queue.popleft()
31
32 # If we encounter a None node, stop the traversal
33 if current_node is None:
34 break
35
36 # Add both children to the queue (even if they are None)
37 queue.append(current_node.left)
38 queue.append(current_node.right)
39
40 # After encountering the first None, all remaining nodes in the queue should be None
41 # If there's any non-None node remaining, the tree is not complete
42 return all(node is None for node in queue)
43
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 * Checks if a binary tree is a complete binary tree.
19 * A complete binary tree is a binary tree in which all levels are fully filled
20 * except possibly the last level, which is filled from left to right.
21 *
22 * @param root The root node of the binary tree
23 * @return true if the tree is complete, false otherwise
24 */
25 public boolean isCompleteTree(TreeNode root) {
26 // Initialize a queue for level-order traversal
27 Deque<TreeNode> queue = new LinkedList<>();
28 queue.offer(root);
29
30 // Phase 1: Traverse the tree level by level, adding all nodes (including nulls)
31 // Continue until we encounter the first null node
32 while (queue.peek() != null) {
33 TreeNode currentNode = queue.poll();
34 // Add both children to queue, even if they are null
35 queue.offer(currentNode.left);
36 queue.offer(currentNode.right);
37 }
38
39 // Phase 2: Remove all remaining null nodes from the front of the queue
40 // In a complete tree, after the first null, all remaining should be null
41 while (!queue.isEmpty() && queue.peek() == null) {
42 queue.poll();
43 }
44
45 // If queue is empty, all remaining nodes were null (complete tree)
46 // If queue is not empty, there are non-null nodes after null (not complete)
47 return queue.isEmpty();
48 }
49}
50
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 bool isCompleteTree(TreeNode* root) {
15 // Use BFS to traverse the tree level by level
16 queue<TreeNode*> bfsQueue;
17 bfsQueue.push(root);
18
19 // Phase 1: Process all non-null nodes
20 // Keep adding children (including nulls) until we encounter the first null node
21 while (bfsQueue.front() != nullptr) {
22 TreeNode* currentNode = bfsQueue.front();
23 bfsQueue.pop();
24
25 // Add both children to queue (even if they are null)
26 bfsQueue.push(currentNode->left);
27 bfsQueue.push(currentNode->right);
28 }
29
30 // Phase 2: After encountering the first null, check if any non-null nodes remain
31 // In a complete binary tree, all null nodes should be at the end
32 while (!bfsQueue.empty() && bfsQueue.front() == nullptr) {
33 bfsQueue.pop();
34 }
35
36 // If queue is empty, all remaining nodes were null (complete tree)
37 // If queue is not empty, there are non-null nodes after null (not complete)
38 return bfsQueue.empty();
39 }
40};
41
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.left = (left === undefined ? null : left);
11 this.right = (right === undefined ? null : right);
12 }
13}
14
15/**
16 * Checks if a binary tree is a complete binary tree.
17 * A complete binary tree is a binary tree where all levels are fully filled
18 * except possibly the last level, which is filled from left to right.
19 *
20 * @param root - The root node of the binary tree
21 * @returns true if the tree is complete, false otherwise
22 */
23function isCompleteTree(root: TreeNode | null): boolean {
24 // Handle edge case of empty tree
25 if (!root) {
26 return true;
27 }
28
29 // Use BFS (Breadth-First Search) to traverse the tree level by level
30 const bfsQueue: (TreeNode | null)[] = [];
31 bfsQueue.push(root);
32
33 // Phase 1: Process all non-null nodes
34 // Keep adding children (including nulls) until we encounter the first null node
35 while (bfsQueue.length > 0 && bfsQueue[0] !== null) {
36 const currentNode = bfsQueue.shift()!;
37
38 // Add both children to queue (even if they are null)
39 // This ensures we capture the structure of the tree including gaps
40 bfsQueue.push(currentNode.left);
41 bfsQueue.push(currentNode.right);
42 }
43
44 // Phase 2: After encountering the first null, verify no non-null nodes remain
45 // In a complete binary tree, all null nodes should appear at the end
46 while (bfsQueue.length > 0 && bfsQueue[0] === null) {
47 bfsQueue.shift();
48 }
49
50 // If queue is empty, all remaining nodes were null (tree is complete)
51 // If queue is not empty, there are non-null nodes after a null (tree is not complete)
52 return bfsQueue.length === 0;
53}
54
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree.
The algorithm uses BFS (Breadth-First Search) to traverse the tree level by level. Each node in the tree is visited exactly once when it's dequeued from the queue. After encountering the first None
value, the algorithm performs one more linear scan through the remaining elements in the queue using the all()
function. In the worst case (a complete tree), all nodes are processed before encountering None
, and the queue will contain at most O(n)
None
values to check. Therefore, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
where n
is the number of nodes in the binary tree.
The space complexity is determined by the maximum size of the queue during execution. In a complete binary tree, the maximum number of nodes at any level is at the last level, which can contain up to n/2
nodes. Additionally, when we reach the first None
, the queue might contain both actual nodes and None
values from the last level. In the worst case, the queue can hold up to O(n)
elements (considering both node references and None
values), making the space complexity O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Stopping at First None Child
A common mistake is to stop the traversal as soon as a node has a None
child, rather than continuing to process until a None
node is actually dequeued.
Incorrect Implementation:
def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
queue = deque([root])
while queue:
current_node = queue.popleft()
# WRONG: Checking children for None before processing
if current_node.left is None or current_node.right is None:
# This would incorrectly reject valid complete trees
# where the last level isn't full
break
queue.append(current_node.left)
queue.append(current_node.right)
return all(node is None for node in queue)
Why it's wrong: This approach would incorrectly classify trees like [1, 2, 3, 4]
as incomplete, even though they are valid complete binary trees.
Pitfall 2: Not Adding None Children to Queue
Another mistake is skipping None
children when adding to the queue, which breaks the level-order position tracking.
Incorrect Implementation:
def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
queue = deque([root])
while queue:
current_node = queue.popleft()
if current_node is None:
break
# WRONG: Only adding non-None children
if current_node.left:
queue.append(current_node.left)
if current_node.right:
queue.append(current_node.right)
return all(node is None for node in queue)
Why it's wrong: By not adding None
nodes to the queue, we lose the positional information needed to detect gaps. For example, a tree like [1, 2, 3, null, null, 4]
would incorrectly be classified as incomplete because we'd never encounter the None
nodes between node 2's children and node 3's left child.
Pitfall 3: Using a Flag-Based Approach Incorrectly
Some might try to use a boolean flag to track whether a None
has been seen, but implement it incorrectly.
Incorrect Implementation:
def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
queue = deque([root])
seen_none = False
while queue:
current_node = queue.popleft()
if current_node is None:
seen_none = True
continue
# WRONG: Not checking the flag before adding children
if seen_none:
return False
queue.append(current_node.left)
queue.append(current_node.right)
return True
Why it's wrong: This checks the flag too late. Once we've seen a None
, we shouldn't process any more non-None
nodes at all. The check should happen immediately after dequeuing.
Correct Solution Reminder
The correct approach:
- Add ALL children (including
None
) to the queue - Break immediately when dequeuing a
None
node - Check that all remaining queued nodes are
None
This ensures we properly detect any gaps in the tree structure while correctly handling valid complete trees where the last level isn't full.
Which type of traversal does breadth first search do?
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 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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!