Check whether the child heights at every node differ by at most one.
Start with the concrete trace below. It shows the state the algorithm must carry as it runs.
Problem trace
Balanced Binary Tree: A postorder height function returns a nonnegative height for a balanced subtree and -1 as a failure sentinel that ancestors propagate immediately.
- 1stack start
- 2stack
- 3stack
- 4current
- 3stack
- 5
- 6
- 7
- 2stack
- 1
- 2
- 3
- 4return height 1
- 3
- 5
- 6
- 7
- 2
- 1
- 2
- 3return height 2
- 4left height 1
- 3return height 2
- 5
- 6
- 7
- 2
- 1
- 2return -1
- 3left height 2
- 4
- 3left height 2
- 5
- 6
- 7
- 2return -1
- 1propagate -1
- 2left returned -1
- 3
- 4
- 3
- 5skipped
- 6skipped
- 7skipped
- 2left returned -1
- 1height(root) = -1
- 2
- 3
- 4
- 3
- 5
- 6
- 7
- 2
- Recognize it
- Use a sentinel summary when a parent needs a normal child aggregate but any descendant failure should abort remaining work, as in balanced-height checks or invalid-subtree detection.
- Keep true
- height(node) returns the exact nonnegative subtree height if every node below is balanced; otherwise it returns -1. Thus ancestors can distinguish valid data from failure without a second traversal.
- Reuse it
- Fuse a subtree summary with validation by reserving an impossible summary value for failure, then check it before doing more recursion; this generalizes to BST validation and parse-tree error propagation.
Pattern: Bottom-up tree DFS with an error value.
Simple idea: Return the subtree height when it is balanced. Return -1 when it is not.
Once a child returns -1, pass it upward without more work.
def is_balanced(root: TreeNode | None) -> bool:
def height(node: TreeNode | None) -> int:
if node is None:
return 0
left = height(node.left)
if left < 0:
return -1
right = height(node.right)
if right < 0 or abs(left - right) > 1:
return -1
return 1 + max(left, right)
return height(root) >= 0
Cost: time and call-stack space.
The platform supplies TreeNode with val, left, and right; this snippet assumes that definition.