You are 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 (2^h)^ nodes inclusive at the last level h`.
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Binary Trees - Understanding tree structure, nodes, and null children
Breadth-First Search (BFS) - Level-order traversal using a queue to process nodes
Depth-First Search (DFS) - Recursive tree traversal and tracking node indices
Queue Data Structure - FIFO operations for level-by-level tree processing
1. Breadth First Search
Intuition
In a complete binary tree, all nodes are as far left as possible, with no gaps. If we traverse level by level and encounter a null node, all subsequent nodes in the traversal must also be null. If we find any non-null node after a null, the tree is not complete.
Algorithm
Initialize a queue with the root node.
Process nodes in BFS order:
Dequeue a node and add its left and right children (even if null) to the queue.
If the dequeued node is null, drain the remaining queue.
If any non-null node appears after a null, return false.
Return true if the entire queue is processed without finding a non-null after null.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {boolean}
*/isCompleteTree(root){const queue =newQueue([root]);while(!queue.isEmpty()){const node = queue.pop();if(node){
queue.push(node.left);
queue.push(node.right);}else{while(!queue.isEmpty()){if(queue.pop()){returnfalse;}}}}returntrue;}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolIsCompleteTree(TreeNode root){Queue<TreeNode> q =newQueue<TreeNode>();
q.Enqueue(root);while(q.Count >0){TreeNode node = q.Dequeue();if(node !=null){
q.Enqueue(node.left);
q.Enqueue(node.right);}else{while(q.Count >0){if(q.Dequeue()!=null){returnfalse;}}}}returntrue;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcisCompleteTree(root *TreeNode)bool{
q :=[]*TreeNode{root}forlen(q)>0{
node := q[0]
q = q[1:]if node !=nil{
q =append(q, node.Left)
q =append(q, node.Right)}else{forlen(q)>0{if q[0]!=nil{returnfalse}
q = q[1:]}}}returntrue}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funisCompleteTree(root: TreeNode?): Boolean {val q = ArrayDeque<TreeNode?>()
q.add(root)while(q.isNotEmpty()){val node = q.removeFirst()if(node !=null){
q.add(node.left)
q.add(node.right)}else{while(q.isNotEmpty()){if(q.removeFirst()!=null){returnfalse}}}}returntrue}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcisCompleteTree(_ root:TreeNode?)->Bool{var queue =[root]while!queue.isEmpty {let node = queue.removeFirst()iflet node = node {
queue.append(node.left)
queue.append(node.right)}else{while!queue.isEmpty {if queue.removeFirst()!=nil{returnfalse}}}}returntrue}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
2. Breadth First Search (Optimal)
Intuition
This is a cleaner version of the BFS approach. Instead of draining the queue after seeing null, we use a flag to track whether a null has been seen. If we encounter a non-null node after the flag is set, the tree is incomplete.
Algorithm
Initialize a queue with the root and a boolean flag nullSeen = false.
Process each node from the queue:
If the node is non-null and nullSeen is true, return false.
If the node is non-null, add both children to the queue.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {boolean}
*/isCompleteTree(root){const queue =newQueue([root]);let nullSeen =false;while(!queue.isEmpty()){const node = queue.pop();if(node){if(nullSeen)returnfalse;
queue.push(node.left);
queue.push(node.right);}else{
nullSeen =true;}}returntrue;}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolIsCompleteTree(TreeNode root){Queue<TreeNode> q =newQueue<TreeNode>();
q.Enqueue(root);bool nullSeen =false;while(q.Count >0){TreeNode node = q.Dequeue();if(node !=null){if(nullSeen){returnfalse;}
q.Enqueue(node.left);
q.Enqueue(node.right);}else{
nullSeen =true;}}returntrue;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcisCompleteTree(root *TreeNode)bool{
q :=[]*TreeNode{root}
nullSeen :=falseforlen(q)>0{
node := q[0]
q = q[1:]if node !=nil{if nullSeen {returnfalse}
q =append(q, node.Left)
q =append(q, node.Right)}else{
nullSeen =true}}returntrue}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funisCompleteTree(root: TreeNode?): Boolean {val q = ArrayDeque<TreeNode?>()
q.add(root)var nullSeen =falsewhile(q.isNotEmpty()){val node = q.removeFirst()if(node !=null){if(nullSeen)returnfalse
q.add(node.left)
q.add(node.right)}else{
nullSeen =true}}returntrue}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcisCompleteTree(_ root:TreeNode?)->Bool{var queue:[TreeNode?]=[root]var nullSeen =falsewhile!queue.isEmpty {let node = queue.removeFirst()iflet node = node {if nullSeen {returnfalse}
queue.append(node.left)
queue.append(node.right)}else{
nullSeen =true}}returntrue}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Depth First Search (Two Pass)
Intuition
A complete binary tree with n nodes has a specific property: if we number nodes starting from 0 (root) where a node at index i has children at 2i+1 and 2i+2, then every node's index must be less than n. First count all nodes, then verify that no node has an index >= n.
Algorithm
First pass: Count the total number of nodes in the tree using DFS.
Second pass: Perform DFS with indices, starting from (root, 0).
If a node is null, return true.
If a node's index >= total count, return false (gap exists).
Recursively check left child (index 2i+1) and right child (index 2i+2).
# 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 = rightclassSolution:defisCompleteTree(self, root: Optional[TreeNode])->bool:defdfs(node, index, n):ifnot node:returnTrueif index >= n:returnFalse
left = dfs(node.left,2* index +1, n)
right = dfs(node.right,2* index +2, n)return left and right
defcountNodes(node):ifnot node:return0return1+ countNodes(node.left)+ countNodes(node.right)
n = countNodes(root)return dfs(root,0, n)
/**
* 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;
* }
* }
*/publicclassSolution{privateintcountNodes(TreeNode root){if(root ==null)return0;return1+countNodes(root.left)+countNodes(root.right);}privatebooleandfs(TreeNode node,int index,int n){if(node ==null)returntrue;if(index >= n)returnfalse;returndfs(node.left,2* index +1, n)&&dfs(node.right,2* index +2, n);}publicbooleanisCompleteTree(TreeNode root){int n =countNodes(root);returndfs(root,0, n);}}
/**
* 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) {}
* };
*/classSolution{public:intcountNodes(TreeNode* root){if(!root)return0;return1+countNodes(root->left)+countNodes(root->right);}booldfs(TreeNode* node,int index,int n){if(!node)returntrue;if(index >= n)returnfalse;returndfs(node->left,2* index +1, n)&&dfs(node->right,2* index +2, n);}boolisCompleteTree(TreeNode* root){int n =countNodes(root);returndfs(root,0, n);}};
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @return {boolean}
*/isCompleteTree(root){constcountNodes=(node)=>{if(!node)return0;return1+countNodes(node.left)+countNodes(node.right);};constdfs=(node, index, n)=>{if(!node)returntrue;if(index >= n)returnfalse;return(dfs(node.left,2* index +1, n)&&dfs(node.right,2* index +2, n));};const n =countNodes(root);returndfs(root,0, n);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolIsCompleteTree(TreeNode root){int n =CountNodes(root);returnDfs(root,0, n);}privateboolDfs(TreeNode node,int index,int n){if(node ==null)returntrue;if(index >= n)returnfalse;returnDfs(node.left,2* index +1, n)&&Dfs(node.right,2* index +2, n);}privateintCountNodes(TreeNode node){if(node ==null)return0;return1+CountNodes(node.left)+CountNodes(node.right);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcisCompleteTree(root *TreeNode)bool{var countNodes func(node *TreeNode)int
countNodes =func(node *TreeNode)int{if node ==nil{return0}return1+countNodes(node.Left)+countNodes(node.Right)}var dfs func(node *TreeNode, index, n int)bool
dfs =func(node *TreeNode, index, n int)bool{if node ==nil{returntrue}if index >= n {returnfalse}returndfs(node.Left,2*index+1, n)&&dfs(node.Right,2*index+2, n)}
n :=countNodes(root)returndfs(root,0, n)}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funisCompleteTree(root: TreeNode?): Boolean {funcountNodes(node: TreeNode?): Int {if(node ==null)return0return1+countNodes(node.left)+countNodes(node.right)}fundfs(node: TreeNode?, index: Int, n: Int): Boolean {if(node ==null)returntrueif(index >= n)returnfalsereturndfs(node.left,2* index +1, n)&&dfs(node.right,2* index +2, n)}val n =countNodes(root)returndfs(root,0, n)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcisCompleteTree(_ root:TreeNode?)->Bool{funccountNodes(_ node:TreeNode?)->Int{guardlet node = node else{return0}return1+countNodes(node.left)+countNodes(node.right)}funcdfs(_ node:TreeNode?,_ index:Int,_ n:Int)->Bool{guardlet node = node else{returntrue}if index >= n {returnfalse}returndfs(node.left,2* index +1, n)&&dfs(node.right,2* index +2, n)}let n =countNodes(root)returndfs(root,0, n)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n) for recursion stack.
4. Depth First Search (Optimal)
Intuition
We can verify completeness in a single DFS pass by tracking the tree's height and whether we have seen a "short" path (one that ends before the maximum depth). In a complete tree, all paths to null nodes at the deepest level must appear before any paths ending one level higher, when traversing left to right.
Algorithm
Track the tree height (initialized to 0) and a flag nullSeen (for detecting gaps).
Perform DFS, tracking the current height:
When reaching a null node, set treeHgt if not set.
If the null is at height (treeHgt - 1), mark nullSeen = true.
If the null is at treeHgt after nullSeen is true, return false (gap detected).
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{privateint treeHgt =0;privatebool nullSeen =false;publicboolIsCompleteTree(TreeNode root){returnDfs(root,0);}privateboolDfs(TreeNode node,int hgt){if(node ==null){if(treeHgt ==0){
treeHgt = hgt;}elseif(hgt == treeHgt -1){
nullSeen =true;}elseif(hgt != treeHgt){returnfalse;}return!(hgt == treeHgt && nullSeen);}returnDfs(node.left, hgt +1)&&Dfs(node.right, hgt +1);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcisCompleteTree(root *TreeNode)bool{
treeHgt :=0
nullSeen :=falsevar dfs func(node *TreeNode, hgt int)bool
dfs =func(node *TreeNode, hgt int)bool{if node ==nil{if treeHgt ==0{
treeHgt = hgt
}elseif hgt == treeHgt-1{
nullSeen =true}elseif hgt != treeHgt {returnfalse}return!(hgt == treeHgt && nullSeen)}returndfs(node.Left, hgt+1)&&dfs(node.Right, hgt+1)}returndfs(root,0)}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {privatevar treeHgt =0privatevar nullSeen =falsefunisCompleteTree(root: TreeNode?): Boolean {
treeHgt =0
nullSeen =falsereturndfs(root,0)}privatefundfs(node: TreeNode?, hgt: Int): Boolean {if(node ==null){if(treeHgt ==0){
treeHgt = hgt
}elseif(hgt == treeHgt -1){
nullSeen =true}elseif(hgt != treeHgt){returnfalse}return!(hgt == treeHgt && nullSeen)}returndfs(node.left, hgt +1)&&dfs(node.right, hgt +1)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcisCompleteTree(_ root:TreeNode?)->Bool{var treeHgt =0var nullSeen =falsefuncdfs(_ node:TreeNode?,_ hgt:Int)->Bool{if node ==nil{if treeHgt ==0{
treeHgt = hgt
}elseif hgt == treeHgt -1{
nullSeen =true}elseif hgt != treeHgt {returnfalse}return!(hgt == treeHgt && nullSeen)}returndfs(node?.left, hgt +1)&&dfs(node?.right, hgt +1)}returndfs(root,0)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n) for recursion stack.
Common Pitfalls
Not Adding Null Children to Queue
When using BFS, both left and right children must be added to the queue even if they are null. Skipping null children prevents detection of gaps in the tree, since the null marker is needed to identify when a non-null node appears after a gap.
# Wrong: only adds non-null childrenif node.left:
q.append(node.left)# Correct: always add both children
q.append(node.left)
q.append(node.right)
Checking Only Immediate Children for Completeness
A tree can have a missing left child but present right child, which violates completeness. Simply checking if both children exist misses cases where a null appears in the middle of a level.
# Wrong: only checks current node's childrenif node.left isNoneand node.right isnotNone:returnFalse# Correct: track if any null seen, then check for non-null afterif node and nullSeen:returnFalse
Wrong Index Formula in DFS Approach
When using array-based indexing (root at 0, children at 2i+1 and 2i+2), using incorrect formulas causes false positives. A common mistake is using 2i and 2i+1, which corresponds to 1-indexed arrays instead of 0-indexed.
# Wrong: 1-indexed formula with 0-indexed array
left_child =2* index
right_child =2* index +1# Correct: 0-indexed formula
left_child =2* index +1
right_child =2* index +2