Check if Binary Tree is Balanced in DSA Rust - Time & Space Complexity
We want to know how long it takes to check if a binary tree is balanced.
This helps us understand how the time grows as the tree gets bigger.
Analyze the time complexity of the following code snippet.
fn is_balanced(root: &Option>) -> bool {
fn height(node: &Option>) -> i32 {
if let Some(n) = node {
let left = height(&n.left);
let right = height(&n.right);
if left == -1 || right == -1 || (left - right).abs() > 1 {
return -1;
}
left.max(right) + 1
} else {
0
}
}
height(root) != -1
}
This code checks if a binary tree is balanced by computing heights and verifying balance at each node.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to visit each node once.
- How many times: Each node is visited once to calculate height and check balance.
As the number of nodes grows, the function visits each node once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 visits |
| 100 | About 100 visits |
| 1000 | About 1000 visits |
Pattern observation: The work grows directly with the number of nodes.
Time Complexity: O(n)
This means the time to check balance grows linearly with the number of nodes in the tree.
[X] Wrong: "The function visits nodes multiple times, so it must be O(n²)."
[OK] Correct: The code visits each node only once because it returns -1 early if unbalanced, avoiding repeated work.
Understanding this linear time check shows you can analyze recursive tree algorithms efficiently, a key skill in many coding challenges.
"What if the function calculated height separately for left and right subtrees without early stopping? How would the time complexity change?"
Practice
Solution
Step 1: Understand balance condition
A balanced binary tree means for every node, the height difference between its left and right subtrees is at most 1.Step 2: Eliminate incorrect options
All nodes having two children describes a full tree. Equal number of nodes in subtrees does not define balance. Sorted order defines a BST. None match the balance definition.Final Answer:
The height difference between left and right subtrees of every node is at most 1 -> Option AQuick Check:
Balance means height difference ≤ 1 [OK]
- Confusing balanced with complete or full tree
- Thinking balance means equal number of nodes
- Assuming sorted order means balanced
Solution
Step 1: Understand Rust ownership and references
Binary trees are usually represented with Option<Box<TreeNode>>. Passing by reference (&Option<Box<TreeNode>>) avoids moving ownership.Step 2: Analyze the signatures
Passing Option<Box<TreeNode>> moves ownership. Option<TreeNode> is invalid due to infinite size. TreeNode directly ignores None roots. &Option<Box<TreeNode>> correctly borrows the tree.Final Answer:
fn is_balanced(root: &Option<Box<TreeNode>>) -> bool -> Option CQuick Check:
Use reference to Option<Box<TreeNode>> [OK]
- Passing TreeNode directly instead of boxed option
- Not using references causing ownership errors
- Using Option<TreeNode> instead of Option
struct TreeNode {
val: i32,
left: Option,
right: Option,
}
fn height(node: &Option) -> i32 {
match node {
None => 0,
Some(n) => 1 + i32::max(height(&n.left), height(&n.right)),
}
}
fn is_balanced(root: &Option) -> bool {
match root {
None => true,
Some(n) => {
let lh = height(&n.left);
let rh = height(&n.right);
(lh - rh).abs() <= 1 && is_balanced(&n.left) && is_balanced(&n.right)
}
}
}
fn main() {
let tree = Some(Box::new(TreeNode {
val: 1,
left: Some(Box::new(TreeNode {
val: 2,
left: None,
right: None,
})),
right: None,
}));
println!("{}", is_balanced(&tree));
}Solution
Step 1: Analyze tree structure
The tree has root 1, left child 2, and no right child. Left subtree height = 1, right subtree height = 0.Step 2: Check balance condition
Height difference is 1 which is ≤ 1. Both left and right subtrees are leaves or None, so balanced. So overall tree is balanced.Final Answer:
true -> Option AQuick Check:
Height difference ≤ 1 means balanced [OK]
- Assuming missing right child means unbalanced
- Confusing height calculation logic
- Ignoring recursive balance checks
fn is_balanced(root: &Option) -> bool {
if root.is_none() {
return true;
}
let node = root.as_ref().unwrap();
let lh = height(&node.left);
let rh = height(&node.right);
if (lh - rh).abs() > 1 {
return false;
}
is_balanced(&node.left);
is_balanced(&node.right);
true
}Solution
Step 1: Review recursive calls
The recursive calls to is_balanced(&node.left) and is_balanced(&node.right) are made but their results are not used or returned.Step 2: Understand impact
Ignoring recursive results means function always returns true if root is not None and height difference ≤ 1, missing unbalanced deeper nodes.Final Answer:
Missing return of recursive calls results in ignoring their results -> Option DQuick Check:
Return recursive results to propagate balance status [OK]
- Not returning recursive call results
- Assuming unwrap() always safe without check
- Confusing parameter types
Solution
Step 1: Understand repeated height calculation problem
Calculating height separately for each node causes repeated work, increasing time complexity.Step 2: Use combined recursive function
Returning height if balanced or None if unbalanced lets us stop early and avoid repeated height calls.Step 3: Analyze other options
Separate height and balance traversals is less efficient. Global variables introduce unsafe state. Root-only check misses subtree imbalances.Final Answer:
Calculate height and balance in one recursive function returning Option<i32> height or None if unbalanced -> Option BQuick Check:
Combine height and balance check for efficiency [OK]
- Doing separate height and balance traversals
- Using global variables for tree state
- Checking balance only at root
Ready to prove it?
Pass the test and this pattern moves unknown
