title: Check Completeness of a Binary Tree
slug: check-completeness-of-a-binary-tree
difficulty: medium
leetcode_id: 958
leetcode_url: https://leetcode.com/problems/check-completeness-of-a-binary-tree/
categories:
  - trees
patterns:
  - bfs
  - tree-traversal

description: |
  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`.

constraints: |
  - `1 <= number of nodes <= 100`
  - `1 <= Node.val <= 1000`

examples:
  - input: "root = [1,2,3,4,5,6]"
    output: "true"
    explanation: "Every level before the last is full (levels with node-values {1} and {2, 3}), and all nodes in the last level ({4, 5, 6}) are as far left as possible."
  - input: "root = [1,2,3,4,5,null,7]"
    output: "false"
    explanation: "The node with value 7 isn't as far left as possible. There's a gap (null) before node 7 on the last level."

explanation:
  intuition: |
    Think of a complete binary tree like filling seats in a theatre row by row, from left to right. You can't leave an empty seat and then have someone sit further right — that would create a "gap."

    The key insight is this: **once you encounter a `null` (empty position) during level-order traversal, you should never see another non-null node**. If you do, there's a gap, and the tree is not complete.

    Imagine walking through the tree level by level, left to right (BFS style). In a complete binary tree, all the nodes are "packed" to the left. The moment you see a missing child, every subsequent position in the traversal should also be empty. If a node appears after a gap, the tree fails the completeness test.

  approach: |
    We solve this using **Breadth-First Search (BFS)** with a simple flag:

    **Step 1: Initialise the BFS queue and a flag**

    - `queue`: Start with the root node
    - `found_null`: Set to `False` — this tracks whether we've encountered a missing node

    &nbsp;

    **Step 2: Process nodes level by level**

    - Dequeue the front node
    - If the node is `null`, set `found_null = True` and continue
    - If the node is not `null` but `found_null` is already `True`, return `False` — we found a node after a gap
    - Add the node's left and right children to the queue (even if they're `null`)

    &nbsp;

    **Step 3: Return the result**

    - If we process all nodes without finding a node after a gap, return `True`

    &nbsp;

    The BFS ensures we traverse level by level, left to right — exactly the order needed to detect gaps in a complete binary tree.

  common_pitfalls:
    - title: Using DFS Instead of BFS
      description: |
        Depth-first search (preorder, inorder, postorder) doesn't naturally traverse nodes in level-order. While you *can* solve this with DFS using node positions, it's more complex.

        BFS naturally visits nodes in the exact order we need — level by level, left to right — making the "gap detection" logic straightforward.
      wrong_approach: "DFS traversal without careful position tracking"
      correct_approach: "BFS level-order traversal with null detection"

    - title: Not Enqueueing Null Children
      description: |
        A common mistake is only adding non-null children to the queue. But to detect gaps, we need to see *where* the nulls are in the level-order sequence.

        If we skip nulls entirely, we can't tell if a valid node appears after a gap. The queue must include null placeholders to preserve the tree's structure during traversal.
      wrong_approach: "Only add non-null children to queue"
      correct_approach: "Add both children (including nulls) to preserve position information"

    - title: Stopping at First Null
      description: |
        Simply returning `True` when you see a null is incorrect. Seeing a null is fine — a complete tree can have nulls at the end of the last level.

        The violation occurs only when a **non-null node appears after** a null. We need to continue processing and check for this specific condition.
      wrong_approach: "Return True immediately when null is encountered"
      correct_approach: "Set flag on null, return False only if non-null follows"

  key_takeaways:
    - "**BFS for level-order problems**: When a problem involves tree levels or left-to-right ordering, BFS is usually the natural choice"
    - "**Gap detection pattern**: The 'flag after null' technique applies to any sequence where gaps indicate invalidity"
    - "**Include nulls in traversal**: Sometimes explicitly tracking empty positions is essential to preserve structural information"
    - "**Complete vs Full vs Perfect**: Complete trees fill left-to-right; full trees have 0 or 2 children per node; perfect trees are complete *and* full"

  time_complexity: "O(n). We visit each node exactly once during the BFS traversal, where `n` is the number of nodes in the tree."
  space_complexity: "O(w), where `w` is the maximum width of the tree. In the worst case (a complete tree), the last level can have up to `n/2` nodes, so this is O(n)."

solutions:
  - approach_name: BFS with Null Flag
    is_optimal: true
    code: |
      from collections import deque

      class TreeNode:
          def __init__(self, val=0, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right

      def is_complete_tree(root: TreeNode) -> bool:
          if not root:
              return True

          queue = deque([root])
          # Flag to track if we've seen a null position
          found_null = False

          while queue:
              node = queue.popleft()

              if node is None:
                  # Mark that we've encountered a gap
                  found_null = True
              else:
                  # If we see a node after a gap, tree is not complete
                  if found_null:
                      return False

                  # Add both children (even if null) to track positions
                  queue.append(node.left)
                  queue.append(node.right)

          # No node appeared after a gap — tree is complete
          return True
    explanation: |
      **Time Complexity:** O(n) — We visit each of the n nodes exactly once.

      **Space Complexity:** O(n) — The queue can hold up to n/2 nodes (the last level of a complete tree).

      This solution uses BFS to traverse the tree level by level. The key insight is that in a complete binary tree, once we encounter a null, all subsequent nodes in the traversal must also be null. If a non-null node appears after a null, we immediately know the tree is incomplete.

  - approach_name: BFS with Node Counting
    is_optimal: false
    code: |
      from collections import deque

      def is_complete_tree(root: TreeNode) -> bool:
          if not root:
              return True

          # First pass: count total nodes
          count = 0
          queue = deque([root])
          while queue:
              node = queue.popleft()
              count += 1
              if node.left:
                  queue.append(node.left)
              if node.right:
                  queue.append(node.right)

          # Second pass: check if node at index i exists for all i < count
          # Using 1-based indexing: left child = 2*i, right child = 2*i+1
          queue = deque([(root, 1)])  # (node, index)
          while queue:
              node, idx = queue.popleft()
              # If index exceeds count, tree is not complete
              if idx > count:
                  return False
              if node.left:
                  queue.append((node.left, 2 * idx))
              if node.right:
                  queue.append((node.right, 2 * idx + 1))

          return True
    explanation: |
      **Time Complexity:** O(n) — Two passes through the tree.

      **Space Complexity:** O(n) — Queue storage for BFS.

      This approach uses the property that in a complete binary tree with n nodes, if we assign indices 1 to n in level-order, every index from 1 to n must be filled. We first count nodes, then verify no node has an index greater than the count. While correct, this requires two passes and is slightly less elegant than the null-flag approach.
