Skip to content
dsa450for five year olds

Heap · 13 of 18

Check if a Binary Tree is Heap

Check that a tree has no holes when filled row by row, and that every parent is at least as big as its children.

Simple way
time O(n)
room O(n)
Clever way
time O(n)
room O(h)

The problem, simply

You get a binary tree made of real nodes with left and right links. Say if it is a max heap. Two things must both be true:

  1. Shape: the tree is complete. Every row is full except maybe the last, and the last row is filled from the left with no gaps.
  2. Order: every parent is greater than or equal to each of its children.

A story to remember it

Picture it

tree
1098765seat 0seat 1seat 2seat 3seat 4seat 5

A valid max heap. The little notes are seat numbers: a node at seat i has children at seats 2i + 1 and 2i + 2. Six nodes use seats 0 to 5 with no gaps.

Examples

A valid heap

In
[10, 9, 8, 7, 6, 5]
Out
true

No gaps, and each parent beats its children.

Order is broken

In
[10, 5, 9, 7, 6]
Out
false

The shape is fine, but 5 is smaller than its children 7 and 6.

A hole in the last row

In
[10, 9, 8, 7, null, 5]
Out
false

Every parent beats its children, but node 9 has no right child while node 8 has a child. The last row has a gap.

Equal values

In
[3, 3, 3]
Out
true

A parent only needs to be greater than or equal to its children.

One node or none

In
[42] or []
Out
true

A single node is a heap. An empty tree is usually treated as a heap too.

Way 1: the simple way

Read the tree row by row with a waiting line, a queue, exactly like level order traversal. In a complete tree, once you meet a missing child, every child after it must be missing too.

  1. Put the root in the line. Set gap = false.
  2. Take a node from the front. Look at its left child, then its right child.
  3. If a child is missing, set gap = true.
  4. If a child is there but gap is already true, the tree has a hole. Return false.
  5. If a child is bigger than the node, the order is broken. Return false.
  6. Otherwise put the child at the back of the line.
java
import java.util.ArrayDeque;
import java.util.Queue;

class Solution {
    public boolean isHeap(TreeNode root) {
        if (root == null) return true;
        Queue<TreeNode> line = new ArrayDeque<>();
        line.add(root);
        boolean gap = false;
        while (!line.isEmpty()) {
            TreeNode node = line.poll();
            for (TreeNode child : new TreeNode[] { node.left, node.right }) {
                if (child == null) { gap = true; continue; }
                if (gap) return false;                 // a node after a hole
                if (child.val > node.val) return false; // child beats parent
                line.add(child);
            }
        }
        return true;
    }
}

How long it takes: every node joins and leaves the line once, O(n). Extra room: the line holds a whole row, up to about n / 2 nodes, O(n).

Way 2: the clever way

Use the cinema seats. A tree with n nodes is complete exactly when its nodes use seats 0 to n - 1, with seat numbers given the same way an array heap stores them.

  1. Count the nodes, n.
  2. Walk the tree with recursion, carrying the seat number. The root gets seat 0. The left child of seat i gets 2i + 1, the right child gets 2i + 2.
  3. If any node gets a seat >= n, there is a hole. Return false.
  4. If any child is bigger than its parent, return false.

Check [10, 9, 8, 7, null, 5], which has a hole

01 / 07

tree
109875

Count the nodes first. There are 5, so a complete tree would use seats 0 to 4.

n
5
  • looking now
  • checking
  • done
  • rejected
  • path
java
class Solution {
    public boolean isHeap(TreeNode root) {
        int n = count(root);
        return check(root, 0, n);
    }

    private int count(TreeNode node) {
        if (node == null) return 0;
        return 1 + count(node.left) + count(node.right);
    }

    private boolean check(TreeNode node, int seat, int n) {
        if (node == null) return true;
        if (seat >= n) return false;                          // a hole before this node
        if (node.left != null && node.left.val > node.val) return false;
        if (node.right != null && node.right.val > node.val) return false;
        return check(node.left, 2 * seat + 1, n)
            && check(node.right, 2 * seat + 2, n);
    }
}

How long it takes: one walk to count and one walk to check, O(n). Extra room: the recursion goes as deep as the tree, O(h). For a real heap h is about log n. For a long thin tree the counting walk can go n deep, but the checking walk stops as soon as a seat number reaches n.

Watch out for

  • A tree that leans left, like [3, 2, null, 1], has the right order but is not complete. Node 1 gets seat 3 while only seats 0 to 2 exist.
  • Seat numbers grow like 2^depth. On a very deep thin tree they overflow int. In Way 2 the walk returns false long before that, as soon as a seat passes n. If you compute seats any other way, use long.
  • For a min heap, flip the two comparisons.
  • An empty tree: decide with the interviewer. Most judges say true.

What interviewers ask next

  • Check a min heap instead? Flip > to < in the two child checks.
  • Given an array instead of a tree? The shape is always complete, so only check a[i] >= a[2i + 1] and a[i] >= a[2i + 2] for every parent i < n / 2.
  • Check only that the tree is complete (LeetCode 958)? Use either way without the value checks.
  • Can you do it in one pass? Yes, with the queue way, or with a post-order walk that returns the height and whether each subtree is perfect or complete. Be careful: a check like "left count is between right count and twice it plus one" is not enough to prove completeness.

Remember this

Grown-up words: the seats are array indexes of a binary heap, no holes means a complete binary tree, parent beats child is the max heap property, the waiting line is a queue for breadth first search, and the walk with seat numbers is a depth first search.