Check Completeness of a Binary Tree
MedPrompt
Given the root of a binary tree, determine if it is a complete binary tree.
In a complete binary tree, every level, except possibly the last, is completely filled, 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.
Example 1:
Input: root = [1,2,3,4,5,6]
Output: true
Explanation: Every level before the last is full (ie. levels with node-values {1} and {2, 3}), and all nodes in the last level ({4, 5, 6}) are as far left as possible.Example 2:
Input: root = [1,2,3,4,5,null,7]
Output: false
Explanation: The node with value 7 isn't as far left as possible.
Constraints:
- The number of nodes in the tree is in the range
[1, 100]. 1 <= Node.val <= 1000
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a Depth-First Search (DFS) traversal. We can attempt to determine if a tree is complete by treating it as if it were stored in an array, where each node has a specific index. For a node at index i, its left child is at 2*i and its right child is at 2*i+1. A tree would be complete if the total number of nodes equals the maximum index found. However, this approach has a critical flaw for deep trees.
Algorithm
- Create a helper DFS function, say
dfs(node, index). - In the main function, initialize
node_count = 0andmax_index = 0. - Call
dfs(root, 1). - After the call returns, compare
node_countandmax_index. Returntrueif they are equal,falseotherwise. - The
dfs(node, index)function:- If
nodeisnull, return. - Increment
node_count. - Update
max_index = max(max_index, index). - Recursively call
dfs(node.left, 2 * index). - Recursively call
dfs(node.right, 2 * index + 1).
- If
Walkthrough
The idea is to assign an "ID" or "index" to each node. The root is assigned index 1. For any node at index i, its left child gets index 2*i and its right child gets 2*i+1.
We perform a DFS traversal of the tree, counting the total number of nodes and finding the maximum index assigned to any node. After the traversal, we compare the count and the max index. If they are equal, the tree is complete.
This logic works for 'bushy' trees, but fails for deep, skewed trees due to integer overflow, as the index can grow exponentially with depth.
// NOTE: This solution is flawed and will fail for deep, skewed trees due to integer overflow.class Solution { private int count = 0; private long maxIndex = 0; // Using long to delay overflow, but it still happens for N > 62 public boolean isCompleteTree(TreeNode root) { if (root == null) { return true; } dfs(root, 1L); return count == maxIndex; } private void dfs(TreeNode node, long index) { if (node == null) { return; } count++; maxIndex = Math.max(maxIndex, index); // Stop recursion to prevent overflow, but this is just a patch. // A correct solution shouldn't rely on indices that can grow this large. if (index >= Long.MAX_VALUE / 2) return; dfs(node.left, 2 * index); dfs(node.right, 2 * index + 1); }}Complexity
Time
O(N), where N is the number of nodes. We visit each node exactly once.
Space
O(H), where H is the height of the tree, due to the recursion stack. For a complete tree, H is O(log N). For a skewed tree, H is O(N).
Trade-offs
Pros
For trees that are 'bushy' or close to complete, it is more space-efficient (O(log N)) than BFS (O(N)).
Cons
The logic is less intuitive than the BFS approach.
Critically, the node indices can grow exponentially with the depth of the tree (
2^depth). For a skewed tree with N nodes, the depth can be N. With N=100, the index2^99would overflow even a 64-bit integer (long), making this approach fail for deep, sparse trees that are permitted by the problem constraints.Relies on recursion, which can lead to stack overflow for extremely deep trees (also an issue with N=100).
This approach uses Breadth-First Search (BFS) to traverse the tree level by level. It is a robust and intuitive method for this problem. The core idea is that in a complete binary tree, once a null node is encountered in the level-order traversal, all subsequent nodes must also be null.
Algorithm
- If the
rootisnull, returntrue(an empty tree is complete). - Initialize a queue (
java.util.Queue) and add therootto it. - Initialize a boolean flag
endFound = false. - Loop while the queue is not empty:
- Dequeue the current node.
- If the current node is
null, setendFound = true. - If the current node is not
null:- If
endFoundistrue, it means we found a node after a null gap. Returnfalse. - Enqueue the left child (
node.left). - Enqueue the right child (
node.right).
- If
- If the loop finishes, return
true.
Walkthrough
We perform a level-order traversal starting from the root using a queue.
We iterate through the tree level by level. When we encounter the first null node, it signifies the potential end of the tree's nodes.
We use a boolean flag, endFound, initialized to false. When we dequeue a null node, we set this flag to true.
If we ever dequeue a non-null node after endFound has been set to true, it means there's a gap in the tree, violating the "as far left as possible" rule. In this case, the tree is not complete, and we return false.
If a node is not null, we enqueue its left and right children, even if they are null. This is crucial to detect gaps.
If the traversal completes without violating the condition, the tree is complete, and we return true.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public boolean isCompleteTree(TreeNode root) { if (root == null) { return true; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); boolean endFound = false; while (!queue.isEmpty()) { TreeNode currentNode = queue.poll(); if (currentNode == null) { endFound = true; } else { if (endFound) { return false; // Found a non-null node after a null node } queue.offer(currentNode.left); queue.offer(currentNode.right); } } return true; }}Complexity
Time
O(N), where N is the number of nodes in the tree. Each node is enqueued and dequeued exactly once.
Space
O(W), where W is the maximum width of the binary tree. In the worst case (a complete binary tree), the last level can contain up to `ceil(N/2)` nodes, so the space complexity is O(N).
Trade-offs
Pros
Intuitive and directly follows the definition of a complete binary tree.
Robust and works correctly for all trees within the given constraints.
Relatively easy to implement.
Cons
Can be less space-efficient than a DFS approach for 'bushy' trees where the width
Wis proportional toN.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public boolean isCompleteTree ( TreeNode root ) { Deque < TreeNode > q = new LinkedList <>(); q . offer ( root ); while ( q . peek () != null ) { TreeNode node = q . poll (); q . offer ( node . left ); q . offer ( node . right ); } while (! q . isEmpty () && q . peek () == null ) { q . poll (); } return q . isEmpty (); } }/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: bool isCompleteTree ( TreeNode * root ) { queue < TreeNode *> q { { root } }; while ( q . front ()) { root = q . front (); q . pop (); q . push ( root -> left ); q . push ( root -> right ); } while ( ! q . empty () && ! q . front ()) q . pop (); return q . empty (); } };# 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 isCompleteTree ( self , root : TreeNode ) -> bool : q = deque ([ root ]) while q : node = q . popleft () if node is None : break q . append ( node . left ) q . append ( node . right ) return all ( node is None for node in q )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.