Check if Binary Tree is Balanced in DSA Go - Time & Space Complexity
We want to know how long it takes to check if a binary tree is balanced.
Specifically, how the time grows as the tree gets bigger.
Analyze the time complexity of the following code snippet.
func isBalanced(root *TreeNode) bool {
if root == nil {
return true
}
leftHeight := height(root.Left)
rightHeight := height(root.Right)
if abs(leftHeight - rightHeight) > 1 {
return false
}
return isBalanced(root.Left) && isBalanced(root.Right)
}
func height(node *TreeNode) int {
if node == nil {
return 0
}
left := height(node.Left)
right := height(node.Right)
if left > right {
return left + 1
}
return right + 1
}
This code checks if a binary tree is balanced by comparing heights of subtrees recursively.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to
heightinsideisBalanced. - How many times: For each node,
heightis called multiple times, causing repeated subtree height calculations.
As the tree size grows, the number of recursive calls grows quickly because height is recalculated many times.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 100 calls |
| 100 | About 10,000 calls |
| 1000 | About 1,000,000 calls |
Pattern observation: The operations grow roughly with the square of the number of nodes.
Time Complexity: O(n²)
This means the time to check balance grows very fast as the tree gets bigger, roughly proportional to the square of the number of nodes.
[X] Wrong: "The function runs in linear time because it visits each node once."
[OK] Correct: The height function is called repeatedly for the same nodes, causing many extra visits and making the total work much larger.
Understanding this helps you explain why some recursive solutions can be inefficient and how to improve them, a key skill in interviews.
"What if we modified the code to calculate height and balance in a single recursive pass? How would the time complexity change?"
Practice
Solution
Step 1: Understand the balance condition
A balanced binary tree means for every node, the height difference between its left and right subtree is at most 1.Step 2: Compare options with definition
The height difference between left and right subtrees of every node is at most 1 matches the definition exactly. Other options describe different tree properties.Final Answer:
The height difference between left and right subtrees of every node is at most 1 -> Option AQuick Check:
Balanced tree = height difference ≤ 1 [OK]
- Confusing balanced with complete tree
- Thinking balanced means equal number of nodes
- Assuming all nodes must have two children
Solution
Step 1: Identify correct parameter and return types
The function should take a pointer to the root node and return a boolean indicating balanced or not.Step 2: Match options with correct signature
func isBalanced(root *TreeNode) bool matches: input is *TreeNode pointer, output is bool. Others have wrong return types or parameter types.Final Answer:
func isBalanced(root *TreeNode) bool -> Option AQuick Check:
Input pointer, output bool = correct signature [OK]
- Passing TreeNode by value instead of pointer
- Returning int or string instead of bool
- Using void (no return) for a check function
package main
import "fmt"
type TreeNode struct {
Val int
Left, Right *TreeNode
}
func height(root *TreeNode) int {
if root == nil {
return 0
}
leftHeight := height(root.Left)
rightHeight := height(root.Right)
if leftHeight == -1 || rightHeight == -1 || abs(leftHeight-rightHeight) > 1 {
return -1
}
if leftHeight > rightHeight {
return leftHeight + 1
}
return rightHeight + 1
}
func isBalanced(root *TreeNode) bool {
return height(root) != -1
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func main() {
root := &TreeNode{Val: 1}
root.Left = &TreeNode{Val: 2}
root.Right = &TreeNode{Val: 3}
root.Left.Left = &TreeNode{Val: 4}
root.Left.Left.Left = &TreeNode{Val: 5}
fmt.Println(isBalanced(root))
}Solution
Step 1: Analyze tree structure and height function
The tree is left-heavy: left subtree has height 3, right subtree height 1 at root. Difference is 2 > 1, so height returns -1.Step 2: Check isBalanced return value
isBalanced returns height(root) != -1, which is false here because height returned -1.Final Answer:
false -> Option CQuick Check:
Height difference > 1 means balanced = false [OK]
- Assuming tree is balanced without checking height difference
- Confusing return values of height function
- Ignoring left-heavy subtree depth
func isBalanced(root *TreeNode) bool {
if root == nil {
return false
}
leftHeight := height(root.Left)
rightHeight := height(root.Right)
if abs(leftHeight - rightHeight) > 1 {
return false
}
return isBalanced(root.Left) && isBalanced(root.Right)
}Solution
Step 1: Check base case for empty tree
Function returns false if root is nil, but empty tree is balanced, so should return true.Step 2: Verify other logic correctness
Height difference check and recursive calls are correct. Only base case return is wrong.Final Answer:
Returns false for empty tree instead of true -> Option BQuick Check:
Empty tree is balanced, so base case must return true [OK]
- Returning false for nil root
- Not checking both subtrees recursively
- Confusing logical AND with OR in recursion
Solution
Step 1: Understand naive approach inefficiency
Calculating height separately for each node causes repeated work, leading to O(n²) time.Step 2: Use bottom-up recursion for efficiency
Return height if balanced, else -1 to stop early. This avoids repeated height calculations, achieving O(n) time.Final Answer:
Use a bottom-up recursion that returns height or -1 if unbalanced -> Option DQuick Check:
Bottom-up recursion avoids repeated height calls [OK]
- Recalculating heights multiple times causing slow code
- Checking balance only at root misses subtree imbalance
- Using BFS which doesn't help with height difference
Ready to prove it?
Pass the test and this pattern moves unknown
