Start learning free

LESSON 434 OF 572 · BINARY TREES

Balanced Binary Tree in C#: O(n) Height Check, Bottom Up

Practice O(n) Read the full explanation

Given the root of a binary tree, return true if it is height-balanced, and false otherwise.

A binary tree is height-balanced when, at every node, the heights of its left and right subtrees differ by at most 1.

The height of a tree is the number of nodes on its longest path from the top down to a leaf. An empty tree has a height of 0, and it is balanced.

The trees below are written in level order (top to bottom, left to right), with null for a missing child.

 

Example 1:

    3
   / \
  9   20
     /  \
    15   7

Input: root = [3,9,20,null,null,15,7]
Output: true
Explanation: At the root, the left subtree (9) has a height of 1 and the right subtree (20, 15, 7) a height of 2: they differ by 1. At 20, both subtrees have a height of 1, and the leaves have two empty subtrees.

Example 2:

          1
         / \
        2   2
       / \
      3   3
     / \
    4   4

Input: root = [1,2,2,3,3,null,null,4,4]
Output: false
Explanation: At the root, the left subtree has a height of 3 and the right subtree a height of 1: they differ by 2.

Example 3:

          1
         / \
        2   2
       /     \
      3       3
     /         \
    4           4

Input: root = [1,2,2,3,null,null,3,4,null,null,4]
Output: false
Explanation: The root is fine: both of its subtrees have a height of 3. But the left 2 has a left subtree of height 2 (3 and 4) and an empty right subtree, of height 0: they differ by 2. The rule must hold at every node, not only at the root.

 

Constraints:

  • The number of nodes in the tree is in the range [0, 5000].
  • Each node value is between -104 and 104.
  • The tree is at most 800 levels deep.

Note:

Your algorithm should run in O(n) time, where n is the number of nodes, visiting each node once.

Measuring a subtree's height again for every node above it repeats work. Find each subtree's height once, from the bottom up, and check the rule at the same time.

STEP 1 OF 9
    Ctrl + Enter to check

    C# · BINARY TREES

    Balanced Binary Tree in C#, explained

    Problem overview

    The method IsBalanced receives the root of a binary tree and returns true when the tree is height-balanced: at every node, the heights of the left and right subtrees differ by at most 1. The lesson provides the TreeNode class, whose nodes have the fields val, left and right. A missing child is null.

    The height of a tree is the number of nodes on its longest path from the top down to a leaf, and an empty tree has height 0. For the tree [3, 9, 20, null, null, 15, 7] the root's left subtree (the node 9) has height 1 and its right subtree (20 with 15 and 7) has height 2. They differ by 1, and the other nodes are fine too, so the answer is true. For [1, 2, 2, 3, 3, null, null, 4, 4] the left subtree of the root has height 3 and the right has height 1, so the answer is false.

    The rule holds at every node, not just at the root. In [1, 2, 2, 3, null, null, 3, 4, null, null, 4] the root is fine (both sides have height 3), but the left 2 has a subtree of height 2 on one side and nothing on the other. The answer is false.

    Approach: compute heights once, from the bottom up

    The obvious solution is to write a Height method and call it on both children of every node. That works, but the same subtrees get measured again and again, once for every node above them. For a tree shaped like a long chain, that is O(n^2) time.

    The better idea is to combine the two jobs. A recursive Height method returns the height of the tree under a node, and it checks the rule on the way back up. When it finds a node that breaks the rule, it returns -1 instead of a height. A real height is never negative, so -1 can't be mistaken for one: it means "unbalanced somewhere in here". Every caller passes the signal up at once.

    The check at one node has four parts: get the left height, stop if it is -1; get the right height, stop if it is -1; compare the two heights, and stop with -1 if they differ by more than 1; otherwise return 1 plus the larger height. Each node is visited once, and each height is found once. For the three examples, Height(root) returns 3, then -1, then -1.

    Step by step

    1. In IsBalanced, return whether Height(root) != -1. This is the whole method; the helper does the work.
    2. Declare Height as a public method that returns an int and takes a TreeNode named node.
    3. Base case: if node == null, return 0. An empty tree is balanced and has height 0. This also stops the recursion at the leaves.
    4. Left subtree: call Height(node.left) and store it in leftHeight.
    5. Pass the signal up: if leftHeight == -1, return -1 without looking at the right subtree.
    6. Right subtree: store Height(node.right) in rightHeight. A missing child gives 0 here, thanks to the base case.
    7. Pass the signal up again: if rightHeight == -1, return -1. Don't skip this check: -1 differs from an empty side's 0 by only 1, so it would otherwise pass for a height.
    8. Check this node: if Math.Abs(leftHeight - rightHeight) > 1, return -1. Every node runs this code on its way back up, so the rule is checked at every node.
    9. Return the height: 1 for the node itself plus Math.Max(leftHeight, rightHeight).

    The solution in C#

    class Solution {
        public int Height(TreeNode node) {
            if (node == null) {
                return 0;
            }
            int leftHeight = Height(node.left);
            if (leftHeight == -1) {
                return -1;
            }
            int rightHeight = Height(node.right);
            if (rightHeight == -1) {
                return -1;
            }
            if (Math.Abs(leftHeight - rightHeight) > 1) {
                return -1;
            }
            return 1 + Math.Max(leftHeight, rightHeight);
        }
    
        public bool IsBalanced(TreeNode root) {
            return Height(root) != -1;
        }
    }
    

    The C# details worth knowing:

    • Reference types can be null. A TreeNode variable holds a reference, so node == null is how the code tests for a missing child. Reading node.left when node is null would throw a NullReferenceException, which is why the base case comes first.
    • A sentinel value. Returning -1 to mean "unbalanced" is a common trick for returning two kinds of information (a height or a failure) in one int. It only works because real heights are never negative.
    • Math.Abs and Math.Max are in the System namespace. In a file of your own, add using System;.
    • Methods can call each other in any order. In C#, a method inside a class may use a method written below it. The lesson writes Height first only because the steps build it that way.
    • Recursion depth. Each recursive call uses stack space. The tests have at most 800 levels, which is far from the limit, but a tree with hundreds of thousands of levels could crash a recursive solution with a stack overflow.

    Complexity

    Time: O(n). Each node is visited once and does a constant amount of work, and the early returns only make it faster.

    Space: O(h), where h is the height of the tree. The recursion goes as deep as the tree does: about log n levels for a balanced tree, and up to n for a chain. The method creates no data structures of its own.

    Edge cases and common mistakes

    • An empty tree. IsBalanced(null) returns true, since Height(null) is 0.
    • One node. [5] has height 1 and is balanced.
    • Two nodes. [1, 2] has height 2 and is balanced: the sides have heights 1 and 0.
    • A chain of three. [1, 2, null, 3] returns false: at the root the sides have heights 2 and 0. A chain of 800 nodes returns false as well.
    • Only checking the root. The third example has a root with two sides of equal height, and is still unbalanced. The check must run at every node.
    • Treating -1 as a height. If a call skips the -1 checks, a failure turns into a small number, and a node above can end up with a difference of at most 1. The tree then looks balanced.
    • Counting edges instead of nodes. The lesson counts nodes, so a leaf has height 1 and an empty tree 0. The difference test works either way, but the base case has to match the definition.
    • Recomputing heights for every node. It gives the right answer but takes O(n^2) time on a chain.

    Testing

    • [3, 9, 20, null, null, 15, 7] gives true (height 3).
    • [1, 2, 2, 3, 3, null, null, 4, 4] gives false.
    • [1, 2, 2, 3, null, null, 3, 4, null, null, 4] gives false.
    • An empty tree gives true.
    • [5] gives true.
    • [1, 2] gives true.
    • [1, 2, null, 3] gives false.
    • [1, 2, 3, 4] gives true.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free