Balanced Binary Tree – 110. LeetCode


Balancing a binary tree is a fundamental concept in computer science, especially in data structure optimization. The Balanced Binary Tree problem asks you to determine whether a given binary tree is height-balanced, which means it satisfies specific criteria for symmetry and efficiency.

Let’s dive into the problem and break it down step by step.

Understanding The Problem

Determine if a given binary tree is height-balanced.

Definition:

A binary tree is height-balanced if:

• For every node in the tree, the heights of the left and right subtrees differ by at most 1.

Examples:

Balanced Tree:

    1
   / \
  2   3
 / \
4   5

• Height of left subtree (node 2): 2

• Height of right subtree (node 3): 1

• Difference:  |2 – 1| = 1  → Balanced.


Unbalanced Tree:

    1
   /
  2
 /
3

• Height of left subtree (node 2): 2

• Height of right subtree: 0

• Difference:  |2 – 0| = 2  → Not balanced.

Understanding this problem involves two key tasks:

1. Calculate the height of each subtree.

2. Compare the heights to check if the balance condition holds for every node.

Why Does Balance Matter?

• A balanced binary tree ensures optimal performance for operations like searching, insertion, and deletion.

• An unbalanced tree may degrade to a linked list-like structure, resulting in  O(n)  operations instead of  O(log n) .


Analyzing the Naive Approach to Check for a Balanced Binary Tree


The naive approach to solving the Balanced Binary Tree problem involves recursively checking the height of the left and right subtrees for every node. While it’s a straightforward method, it’s far from optimal due to repeated computations.

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
    }
}

public class BalancedBinaryTreeNaive {
    public boolean isBalanced(TreeNode root) {
        if (root == null) return true;

        // Check if left and right subtree heights differ by more than 1
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);

        if (Math.abs(leftHeight - rightHeight) > 1) {
            return false;
        }

        // Recursively check if left and right subtrees are balanced
        return isBalanced(root.left) && isBalanced(root.right);
    }

    // Helper method to calculate the height of a subtree
    private int height(TreeNode node) {
        if (node == null) return 0;

        return Math.max(height(node.left), height(node.right)) + 1;
    }
}

• The naive approach is conceptually simple but inefficient due to repeated height calculations.

• It highlights the importance of optimizing recursive algorithms to avoid redundant computations.

• By identifying its limitations, we’re motivated to develop an optimized solution using bottom-up recursion, which we’ll explore in the next step.

Complexity Analysis

1. Time Complexity:  O(n^2) 

• For each node, the height of its subtrees is calculated using  O(n)  recursive calls.

• Since this operation is performed for  O(n)  nodes, the overall complexity is  O(n^2) .

2. Space Complexity:  O(h) 

• The recursive calls to compute the height use space proportional to the height of the tree  h .


Optimal Approach Using Bottom-Up Recursion

The naive approach recalculates the height of subtrees multiple times for each node, leading to unnecessary computations. In the bottom-up recursive approach, we:

1. Combine height calculation with balance checking:

• As we calculate the height of a subtree, we simultaneously verify whether it is balanced.

2. Propagate imbalance early:

• If a subtree is unbalanced, we propagate this result upwards immediately, avoiding further computations.

This method ensures each node is processed only once, achieving linear time complexity.

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
    }
}

public class BalancedBinaryTreeOptimized {
    public boolean isBalanced(TreeNode root) {
        // If the tree's height is -1, it is unbalanced
        return checkHeight(root) != -1;
    }

    private int checkHeight(TreeNode node) {
        if (node == null) return 0; // Base case: null nodes have height 0

        // Check the height of the left subtree
        int leftHeight = checkHeight(node.left);
        if (leftHeight == -1) return -1; // Left subtree is unbalanced

        // Check the height of the right subtree
        int rightHeight = checkHeight(node.right);
        if (rightHeight == -1) return -1; // Right subtree is unbalanced

        // Check if the current node is balanced
        if (Math.abs(leftHeight - rightHeight) > 1) return -1;

        // Return the height of the current node
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

Steps of the Optimal Approach

1. Use Depth-First Search (DFS)

• Perform a post-order traversal (bottom-up) of the tree.

• This means we process the left and right subtrees before the current node.

2. Calculate Heights and Check Balance

For each node:

1. Compute the height of its left and right subtrees.

2. Check if the subtree rooted at this node is balanced:

• A subtree is balanced if:

• The height difference between its left and right subtrees is  \leq 1 .

• If balanced:

• Return the height of the subtree (1 + max of left and right subtree heights).

• If unbalanced:

• Return an invalid indicator (e.g., -1).

3. Stop Early

• If a subtree is unbalanced, propagate -1 upwards.

• This ensures the algorithm terminates as soon as an imbalance is detected.

Advantages of the Bottom-Up Approach

1. No Redundant Calculations:

• Heights of subtrees are computed only once, unlike the naive approach.

2. Early Termination:

• The algorithm stops as soon as an imbalance is detected, avoiding unnecessary traversal of the rest of the tree.

3. Efficiency:

Time Complexity:  O(n) 

• Each node is visited once.

Space Complexity:  O(h) 

• Space is proportional to the height of the tree due to the recursive call stack.

Conclusion

The bottom-up recursive approach for determining if a binary tree is balanced is a textbook example of efficient recursion. By combining height calculation with balance checking and propagating imbalance early, it avoids redundant computations, making it both time and space efficient.

Mastering this approach not only helps with the Balanced Binary Tree problem but also builds a strong foundation for solving other tree-based problems efficiently. 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *