Facebook Pixel

1382. Balance a Binary Search Tree

Problem Description

You are given the root of a binary search tree (BST). Your task is to transform this BST into a balanced binary search tree while preserving all the original node values. A binary search tree is considered balanced if for every node in the tree, the depth difference between its left and right subtrees is at most 1.

The problem asks you to:

  1. Take an existing BST (which may be unbalanced)
  2. Reorganize its structure to make it balanced
  3. Keep all the same values from the original tree
  4. Return the root of the newly balanced BST

If multiple balanced configurations are possible, you can return any valid one.

The solution approach leverages the fact that an in-order traversal of a BST yields values in sorted order. The algorithm first performs an in-order traversal using the dfs function to collect all node values in the nums array in ascending order. Then, it reconstructs a balanced BST using the build function, which recursively:

  • Selects the middle element as the root node (using mid = (i + j) >> 1, which is equivalent to (i + j) // 2)
  • Builds the left subtree from elements before the middle
  • Builds the right subtree from elements after the middle
  • Returns a new TreeNode with the middle value and the constructed left and right subtrees

This divide-and-conquer approach ensures that the resulting tree is balanced because at each level, we're splitting the remaining elements roughly in half, preventing any path from root to leaf from being significantly longer than others.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.

Is it a tree?

  • Yes: The problem explicitly deals with a binary search tree, which is a tree data structure.

DFS

  • Yes: We arrive at DFS (Depth-First Search) as the recommended approach.

Conclusion: The flowchart suggests using DFS for this problem. This aligns perfectly with the solution approach, which uses DFS in two ways:

  1. An in-order DFS traversal (dfs function) to extract all values from the original BST in sorted order
  2. A recursive construction approach (build function) which is also a form of DFS, building the tree from top to bottom by recursively creating subtrees

The DFS pattern is ideal here because:

  • We need to visit every node in the original tree to collect values (in-order traversal)
  • We need to recursively construct a new balanced tree structure from the collected values
  • Both operations naturally follow a depth-first pattern where we fully explore one branch before moving to another
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight comes from understanding two fundamental properties:

  1. BST Property: An in-order traversal of a BST always gives us values in sorted order
  2. Balance Property: A balanced tree has roughly equal numbers of nodes in its left and right subtrees

When we see an unbalanced BST, we realize that the values themselves are fine - it's just their arrangement that's problematic. Think of it like having a sorted list of numbers that we've arranged poorly in tree form.

The natural question becomes: if we could start fresh with these sorted values, how would we build the most balanced tree possible?

The answer is surprisingly simple: always pick the middle element as the root. Why? Because this ensures we have approximately equal numbers of elements on both sides, which is exactly what balance means.

Consider a sorted array [1, 2, 3, 4, 5, 6, 7]. If we pick 4 (the middle) as root, we get [1, 2, 3] on the left and [5, 6, 7] on the right - perfectly balanced. We then apply the same logic recursively to each subarray.

This leads to our two-step approach:

  1. Extract: Use DFS in-order traversal to get all values in sorted order (essentially "flattening" the unbalanced tree)
  2. Rebuild: Recursively construct a new tree by always choosing the middle element as the root

The beauty of this solution is that it guarantees optimal balance - at each level of recursion, we're dividing the remaining elements as evenly as possible. The bit shift operation (i + j) >> 1 is just an efficient way to find the middle index, equivalent to (i + j) // 2.

Learn more about Greedy, Tree, Depth-First Search, Binary Search Tree, Divide and Conquer and Binary Tree patterns.

Solution Approach

The implementation follows the two-phase approach identified in our intuition: extract values via in-order traversal, then rebuild a balanced tree.

Phase 1: Extract Values with In-Order DFS

The dfs function performs an in-order traversal of the original BST:

def dfs(root: TreeNode):
    if root is None:
        return
    dfs(root.left)     # Visit left subtree
    nums.append(root.val)  # Process current node
    dfs(root.right)    # Visit right subtree

This traversal pattern (left → root → right) guarantees that values are collected in ascending order in the nums array. For example, if our unbalanced BST looks like:

    1
     \
      2
       \
        3

The nums array becomes [1, 2, 3].

Phase 2: Build Balanced Tree

The build(i, j) function constructs a balanced BST from the sorted array within index range [i, j]:

def build(i: int, j: int) -> TreeNode:
    if i > j:
        return None
    mid = (i + j) >> 1
    left = build(i, mid - 1)
    right = build(mid + 1, j)
    return TreeNode(nums[mid], left, right)

The algorithm works as follows:

  1. Base Case: If i > j, the range is empty, return None
  2. Find Middle: Calculate mid = (i + j) >> 1 (bit shift for integer division)
  3. Recursive Construction:
    • Build left subtree from range [i, mid-1]
    • Build right subtree from range [mid+1, j]
    • Create root node with value nums[mid] and attach the subtrees

Example Walkthrough

For nums = [1, 2, 3]:

  • Initial call: build(0, 2), mid = 1
    • Root = TreeNode(2)
    • Left child: build(0, 0) → creates node with value 1
    • Right child: build(2, 2) → creates node with value 3

Result:

    2
   / \
  1   3

The time complexity is O(n) where n is the number of nodes, as we visit each node exactly twice (once during traversal, once during construction). The space complexity is O(n) for storing the sorted values in the nums array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given unbalanced BST:

        1
         \
          2
           \
            3
             \
              4
               \
                5

Step 1: In-order Traversal (Extract Values)

We perform DFS in-order traversal to collect all values:

  • Visit node 1: go left (none), add 1 to nums, go right
  • Visit node 2: go left (none), add 2 to nums, go right
  • Visit node 3: go left (none), add 3 to nums, go right
  • Visit node 4: go left (none), add 4 to nums, go right
  • Visit node 5: go left (none), add 5 to nums, go right (none)

Result: nums = [1, 2, 3, 4, 5]

Step 2: Build Balanced Tree

Now we recursively build from nums = [1, 2, 3, 4, 5]:

build(0, 4): range includes indices 0-4

  • mid = (0 + 4) >> 1 = 2
  • Create root with nums[2] = 3
  • Build left subtree: build(0, 1)
    • mid = (0 + 1) >> 1 = 0
    • Create node with nums[0] = 1
    • Left: build(0, -1) returns None
    • Right: build(1, 1)
      • mid = 1, create node with nums[1] = 2
      • Both children are None
  • Build right subtree: build(3, 4)
    • mid = (3 + 4) >> 1 = 3
    • Create node with nums[3] = 4
    • Left: build(3, 2) returns None
    • Right: build(4, 4)
      • mid = 4, create node with nums[4] = 5
      • Both children are None

Final balanced BST:

        3
       / \
      1   4
       \   \
        2   5

The tree is now balanced: every node has a depth difference of at most 1 between its left and right subtrees. The maximum path length from root to any leaf is 2, compared to 4 in the original unbalanced tree.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8class Solution:
9    def balanceBST(self, root: TreeNode) -> TreeNode:
10        """
11        Balance a Binary Search Tree by converting it to a sorted array
12        and then rebuilding it as a balanced BST.
13      
14        Args:
15            root: The root node of the unbalanced BST
16          
17        Returns:
18            The root node of the balanced BST
19        """
20      
21        def inorder_traversal(node: TreeNode) -> None:
22            """
23            Perform inorder traversal to collect all node values in sorted order.
24          
25            Args:
26                node: Current node being visited
27            """
28            if node is None:
29                return
30          
31            # Traverse left subtree
32            inorder_traversal(node.left)
33          
34            # Process current node - add value to sorted list
35            sorted_values.append(node.val)
36          
37            # Traverse right subtree
38            inorder_traversal(node.right)
39      
40        def build_balanced_bst(left_idx: int, right_idx: int) -> TreeNode:
41            """
42            Build a balanced BST from sorted array using divide and conquer.
43          
44            Args:
45                left_idx: Starting index of the current subarray
46                right_idx: Ending index of the current subarray
47              
48            Returns:
49                Root node of the balanced subtree
50            """
51            # Base case: invalid range
52            if left_idx > right_idx:
53                return None
54          
55            # Choose middle element as root to ensure balance
56            mid_idx = (left_idx + right_idx) // 2
57          
58            # Recursively build left subtree from left half
59            left_subtree = build_balanced_bst(left_idx, mid_idx - 1)
60          
61            # Recursively build right subtree from right half
62            right_subtree = build_balanced_bst(mid_idx + 1, right_idx)
63          
64            # Create new node with middle value and attach subtrees
65            return TreeNode(sorted_values[mid_idx], left_subtree, right_subtree)
66      
67        # Step 1: Extract all values from BST in sorted order
68        sorted_values = []
69        inorder_traversal(root)
70      
71        # Step 2: Build balanced BST from sorted values
72        return build_balanced_bst(0, len(sorted_values) - 1)
73
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    // List to store the sorted values from the BST
18    private List<Integer> sortedValues = new ArrayList<>();
19
20    /**
21     * Balances a Binary Search Tree to minimize its height.
22     * 
23     * @param root The root of the unbalanced BST
24     * @return The root of the balanced BST
25     */
26    public TreeNode balanceBST(TreeNode root) {
27        // Step 1: Perform in-order traversal to get sorted values
28        inorderTraversal(root);
29      
30        // Step 2: Build a balanced BST from the sorted values
31        return buildBalancedBST(0, sortedValues.size() - 1);
32    }
33
34    /**
35     * Performs in-order traversal of the BST to collect values in sorted order.
36     * 
37     * @param node The current node being processed
38     */
39    private void inorderTraversal(TreeNode node) {
40        // Base case: if node is null, return
41        if (node == null) {
42            return;
43        }
44      
45        // Traverse left subtree
46        inorderTraversal(node.left);
47      
48        // Process current node - add value to sorted list
49        sortedValues.add(node.val);
50      
51        // Traverse right subtree
52        inorderTraversal(node.right);
53    }
54
55    /**
56     * Builds a balanced BST from sorted values using divide and conquer approach.
57     * 
58     * @param left The starting index of the current subarray
59     * @param right The ending index of the current subarray
60     * @return The root node of the balanced subtree
61     */
62    private TreeNode buildBalancedBST(int left, int right) {
63        // Base case: invalid range
64        if (left > right) {
65            return null;
66        }
67      
68        // Calculate middle index to ensure balanced tree
69        int middleIndex = (left + right) >> 1;  // Equivalent to (left + right) / 2
70      
71        // Recursively build left subtree from left half
72        TreeNode leftSubtree = buildBalancedBST(left, middleIndex - 1);
73      
74        // Recursively build right subtree from right half
75        TreeNode rightSubtree = buildBalancedBST(middleIndex + 1, right);
76      
77        // Create root node with middle value and attach subtrees
78        return new TreeNode(sortedValues.get(middleIndex), leftSubtree, rightSubtree);
79    }
80}
81
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    /**
15     * Balances a Binary Search Tree (BST) to minimize its height
16     * @param root The root node of the unbalanced BST
17     * @return The root node of the balanced BST
18     */
19    TreeNode* balanceBST(TreeNode* root) {
20        // Step 1: Perform in-order traversal to get sorted values
21        inorderTraversal(root);
22      
23        // Step 2: Build a balanced BST from the sorted array
24        return buildBalancedBST(0, sortedValues.size() - 1);
25    }
26
27private:
28    // Vector to store node values in sorted order
29    vector<int> sortedValues;
30
31    /**
32     * Performs in-order traversal of the BST to collect values in sorted order
33     * @param node Current node being visited
34     */
35    void inorderTraversal(TreeNode* node) {
36        // Base case: if node is null, return
37        if (!node) {
38            return;
39        }
40      
41        // Traverse left subtree
42        inorderTraversal(node->left);
43      
44        // Process current node - add value to sorted array
45        sortedValues.push_back(node->val);
46      
47        // Traverse right subtree
48        inorderTraversal(node->right);
49    }
50
51    /**
52     * Builds a balanced BST from sorted array using divide-and-conquer approach
53     * @param left Left boundary index of the current subarray
54     * @param right Right boundary index of the current subarray
55     * @return Root node of the balanced subtree
56     */
57    TreeNode* buildBalancedBST(int left, int right) {
58        // Base case: invalid range
59        if (left > right) {
60            return nullptr;
61        }
62      
63        // Find middle element to ensure balance
64        int mid = left + (right - left) / 2;
65      
66        // Recursively build left subtree from left half
67        TreeNode* leftSubtree = buildBalancedBST(left, mid - 1);
68      
69        // Recursively build right subtree from right half
70        TreeNode* rightSubtree = buildBalancedBST(mid + 1, right);
71      
72        // Create root node with middle value and attach subtrees
73        return new TreeNode(sortedValues[mid], leftSubtree, rightSubtree);
74    }
75};
76
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Converts an unbalanced BST into a balanced BST
17 * @param root - The root node of the unbalanced BST
18 * @returns The root node of the balanced BST
19 */
20function balanceBST(root: TreeNode | null): TreeNode | null {
21    // Array to store the sorted values from the BST
22    const sortedValues: number[] = [];
23  
24    /**
25     * Performs in-order traversal to collect all values in sorted order
26     * @param node - Current node being traversed
27     */
28    const inorderTraversal = (node: TreeNode | null): void => {
29        if (node === null) {
30            return;
31        }
32      
33        // Traverse left subtree
34        inorderTraversal(node.left);
35      
36        // Process current node - add value to sorted array
37        sortedValues.push(node.val);
38      
39        // Traverse right subtree
40        inorderTraversal(node.right);
41    };
42  
43    /**
44     * Builds a balanced BST from sorted array using divide and conquer
45     * @param startIndex - Starting index of the current subarray
46     * @param endIndex - Ending index of the current subarray
47     * @returns Root node of the balanced subtree
48     */
49    const buildBalancedTree = (startIndex: number, endIndex: number): TreeNode | null => {
50        // Base case: invalid range
51        if (startIndex > endIndex) {
52            return null;
53        }
54      
55        // Calculate middle index using bit shift for integer division
56        const middleIndex: number = (startIndex + endIndex) >> 1;
57      
58        // Recursively build left subtree from left half
59        const leftSubtree: TreeNode | null = buildBalancedTree(startIndex, middleIndex - 1);
60      
61        // Recursively build right subtree from right half
62        const rightSubtree: TreeNode | null = buildBalancedTree(middleIndex + 1, endIndex);
63      
64        // Create new node with middle value as root and attach subtrees
65        return new TreeNode(sortedValues[middleIndex], leftSubtree, rightSubtree);
66    };
67  
68    // Step 1: Collect all values from BST in sorted order
69    inorderTraversal(root);
70  
71    // Step 2: Build and return balanced BST from sorted values
72    return buildBalancedTree(0, sortedValues.length - 1);
73}
74

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of two main phases:

  1. In-order traversal (dfs function): This visits each node exactly once to collect all values in sorted order, taking O(n) time.
  2. Tree reconstruction (build function): This creates a new balanced BST by recursively dividing the sorted array and creating nodes. Each of the n nodes is created exactly once, taking O(n) time.

Therefore, the total time complexity is O(n) + O(n) = O(n).

Space Complexity: O(n)

The space usage includes:

  1. nums array: Stores all n node values, requiring O(n) space.
  2. Recursion stack for dfs: In the worst case (skewed tree), the recursion depth can be O(n).
  3. Recursion stack for build: Since we're building a balanced tree, the maximum recursion depth is O(log n).
  4. New tree nodes: We create n new TreeNode objects, requiring O(n) space.

The dominant space complexity is O(n), determined by the nums array and the space for the new tree nodes.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Modifying the Original Tree Instead of Creating New Nodes

A common mistake is trying to reuse the original tree nodes instead of creating new TreeNode instances. This can lead to circular references or corrupted tree structure.

Incorrect approach:

def build_balanced_bst(left_idx: int, right_idx: int) -> TreeNode:
    if left_idx > right_idx:
        return None
  
    mid_idx = (left_idx + right_idx) // 2
    # Wrong: Trying to modify existing nodes
    root.val = sorted_values[mid_idx]  # This modifies the original tree
    root.left = build_balanced_bst(left_idx, mid_idx - 1)
    root.right = build_balanced_bst(mid_idx + 1, right_idx)
    return root

Correct approach: Always create new TreeNode instances to ensure a clean tree structure:

return TreeNode(sorted_values[mid_idx], left_subtree, right_subtree)

2. Integer Overflow in Middle Index Calculation

While less common in Python, calculating the middle index as (left + right) // 2 can theoretically cause integer overflow in languages with fixed integer sizes.

Safer calculation:

mid_idx = left_idx + (right_idx - left_idx) // 2

3. Forgetting to Handle Empty Tree

Not checking if the input root is None before processing can cause the algorithm to fail.

Solution: Add an early return check:

def balanceBST(self, root: TreeNode) -> TreeNode:
    if root is None:
        return None
  
    # Continue with the algorithm...

4. Using Global Variables Incorrectly

Declaring sorted_values as a class-level variable instead of within the function scope can cause issues when the method is called multiple times on the same Solution instance.

Incorrect:

class Solution:
    sorted_values = []  # Class variable - persists between calls
  
    def balanceBST(self, root: TreeNode) -> TreeNode:
        # sorted_values might contain values from previous calls

Correct: Always initialize the array within the method scope:

def balanceBST(self, root: TreeNode) -> TreeNode:
    sorted_values = []  # Local variable - fresh for each call

5. Off-by-One Errors in Index Boundaries

Using incorrect index boundaries when calling the build function can result in missing nodes or index out of bounds errors.

Common mistakes:

# Wrong: Using length instead of length-1
return build_balanced_bst(0, len(sorted_values))  

# Wrong: Starting from 1 instead of 0
return build_balanced_bst(1, len(sorted_values) - 1)

Correct:

return build_balanced_bst(0, len(sorted_values) - 1)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More