LeetCode #110 Medium

Balanced Binary Tree

Is the tree height-balanced — every node's subtree heights differ by at most 1?

treedfsrecursion
Open on LeetCode ↗
02

Intuition

Naively checking balance at each node recomputes heights repeatedly. Instead, let the height recursion itself signal failure: return −1 the moment any subtree is unbalanced, and propagate it straight up — one pass.

How to spot this pattern

The naive version computes height at every node, re-walking subtrees over and over for O(n²). The fix is a sentinel: have the height function return -1 to mean "already unbalanced below", so one post-order pass both measures and decides. Overloading a return value to carry a failure signal is a common way to collapse two traversals into one.

03

Approach

1

Height with a poison value

height(node): compute child heights; if either is −1 or they differ by > 1, return −1. Otherwise 1 + max.

2

Failure short-circuits

Once −1 appears it bubbles to the root without further real work.

3

Answer at the root

Balanced iff the root's height isn't −1.

04

Solution & live demo

1class Solution:
2 def isBalanced(self, root):
3 def height(node):
4 if not node: return 0
5 l = height(node.left)
6 if l == -1: return -1
7 r = height(node.right)
8 if r == -1 or abs(l - r) > 1: return -1
9 return 1 + max(l, r)
10 return height(root) != -1
05

Common pitfalls

Calling a separate height function at every node

✗ Wrong
return (abs(height(root.left) - height(root.right)) <= 1
        and self.isBalanced(root.left)
        and self.isBalanced(root.right))
✓ Right
def height(node):
    l = height(node.left)
    if l == -1: return -1
    ...

Each isBalanced call re-computes heights that the recursion below already knew, giving O(n²) on a skewed tree. Returning height and balance together makes it a single O(n) pass.

Checking the right subtree before short-circuiting

✗ Wrong
l = height(node.left)
r = height(node.right)
if l == -1 or r == -1: return -1
✓ Right
l = height(node.left)
if l == -1: return -1
r = height(node.right)

It's still correct, but once the left side is known unbalanced the answer is settled — exploring the right subtree is wasted work. Bailing early is the point of the sentinel.

Using 0 as the failure marker

✗ Wrong
if abs(l - r) > 1: return 0
✓ Right
if abs(l - r) > 1: return -1

0 is the legitimate height of an empty subtree, so the parent can't distinguish failure from a null child and unbalanced trees pass. The sentinel must be a value the function could never otherwise return.

06

Edge cases

Perfectly balanced but deep

Single O(n) pass — no repeated height computation.

[1,2,2,3,3,null,null,4,4]

Deep-left subtree trips the |l−r|>1 test at node 2 → −1 propagates.

07

Complexity

Time
O(n)
Space
O(h)
Single post-order pass.