title: Balance a Binary Search Tree
slug: balance-a-binary-search-tree
difficulty: medium
leetcode_id: 1382
leetcode_url: https://leetcode.com/problems/balance-a-binary-search-tree/
categories:
  - trees
patterns:
  - tree-traversal
  - dfs

function_signature: "def balance_bst(root: TreeNode) -> TreeNode:"

test_cases:
  visible:
    - input: { root: [1, null, 2, null, 3, null, 4] }
      expected: [2, 1, 3, null, null, null, 4]
    - input: { root: [2, 1, 3] }
      expected: [2, 1, 3]
  hidden:
    - input: { root: [1] }
      expected: [1]
    - input: { root: [1, null, 2] }
      expected: [1, null, 2]
    - input: { root: [3, 2, null, 1] }
      expected: [2, 1, 3]
    - input: { root: [1, null, 2, null, 3, null, 4, null, 5] }
      expected: [3, 1, 4, null, 2, null, 5]
    - input: { root: [5, 4, null, 3, null, 2, null, 1] }
      expected: [3, 1, 4, null, 2, null, 5]

description: |
  Given the `root` of a binary search tree, return *a **balanced** binary search tree with the same node values*. If there is more than one answer, return **any of them**.

  A binary search tree is **balanced** if the depth of the two subtrees of every node never differs by more than `1`.

constraints: |
  - The number of nodes in the tree is in the range `[1, 10^4]`
  - `1 <= Node.val <= 10^5`

examples:
  - input: "root = [1,null,2,null,3,null,4,null,null]"
    output: "[2,1,3,null,null,null,4]"
    explanation: "This is not the only correct answer, [3,1,4,null,2] is also correct."
  - input: "root = [2,1,3]"
    output: "[2,1,3]"
    explanation: "The tree is already balanced, so the same structure is returned."

explanation:
  intuition: |
    Picture an unbalanced BST that's essentially become a linked list — each node only has a right child, forming a long chain. This degrades search operations from O(log n) to O(n).

    The key insight is that a **BST's in-order traversal always produces a sorted array**. This property is fundamental: as you traverse left-root-right, you visit nodes in ascending order.

    Think of it like this: if you have a sorted array and want to build the most balanced BST possible, you would naturally pick the **middle element** as the root. This ensures roughly half the elements go to the left subtree and half to the right — perfectly balanced!

    By combining these two insights, the problem becomes straightforward:
    1. Convert the BST to a sorted array (in-order traversal)
    2. Build a balanced BST from the sorted array (recursive divide-and-conquer)

    The beauty of this approach is that picking the middle element recursively guarantees the tree will be height-balanced.

  approach: |
    We solve this using **In-order Traversal + Divide-and-Conquer Reconstruction**:

    **Step 1: Extract nodes via in-order traversal**

    - Perform an in-order DFS traversal (left → root → right)
    - Store each node's value in a list as we visit
    - This produces a **sorted array** of all values

    &nbsp;

    **Step 2: Build balanced BST from sorted array**

    - Use a recursive helper function that takes a range `[left, right]`
    - Find the middle index: `mid = (left + right) // 2`
    - Create a new node with the middle value — this becomes the subtree's root
    - Recursively build the left subtree from `[left, mid - 1]`
    - Recursively build the right subtree from `[mid + 1, right]`

    &nbsp;

    **Step 3: Handle base case**

    - When `left > right`, the range is empty — return `None`
    - This terminates the recursion at leaf boundaries

    &nbsp;

    **Step 4: Return the new root**

    - The initial call with the full range `[0, n - 1]` returns the root of the balanced BST

    &nbsp;

    By always choosing the middle element, we ensure each subtree has at most half the remaining elements, guaranteeing the tree is balanced.

  common_pitfalls:
    - title: Modifying the Original Tree In-Place
      description: |
        Attempting to rebalance by rotating nodes in the original tree is complex and error-prone. While AVL or Red-Black tree rotations exist, they're overkill here.

        The cleaner approach is to extract all values and rebuild from scratch — this is O(n) time and O(n) space regardless, which matches any rotation-based solution.
      wrong_approach: "Complex tree rotations on the original structure"
      correct_approach: "Extract values, rebuild from sorted array"

    - title: Forgetting BST In-Order Property
      description: |
        If you try to extract values using pre-order or post-order traversal, you won't get a sorted array. Only **in-order traversal** (left → root → right) produces sorted output from a BST.

        This property is essential — the reconstruction algorithm relies on the array being sorted to place elements correctly.
      wrong_approach: "Using pre-order or BFS to extract values"
      correct_approach: "In-order DFS traversal for sorted output"

    - title: Off-By-One in Middle Calculation
      description: |
        When calculating the middle index, using `(left + right) // 2` works correctly. However, be careful with the recursive ranges:
        - Left subtree: `[left, mid - 1]` — excludes the middle
        - Right subtree: `[mid + 1, right]` — excludes the middle

        Including the middle in either subtree would duplicate values.
      wrong_approach: "Overlapping ranges that include mid twice"
      correct_approach: "Exclusive ranges: [left, mid-1] and [mid+1, right]"

    - title: Not Handling Empty Ranges
      description: |
        The base case `left > right` must return `None`. This happens when:
        - A node has no left child: recursive call gets an empty range
        - A node has no right child: recursive call gets an empty range

        Missing this base case causes infinite recursion or index errors.

  key_takeaways:
    - "**BST property**: In-order traversal of a BST always produces a sorted sequence — this is fundamental to many BST algorithms"
    - "**Divide-and-conquer**: Building a balanced structure from sorted data by recursively picking the middle element is a powerful pattern"
    - "**Rebuild vs modify**: Sometimes reconstructing a data structure is simpler and equally efficient as modifying in place"
    - "**Related problems**: This technique applies to *Convert Sorted Array to BST* (LC 108) and *Convert Sorted List to BST* (LC 109)"

  time_complexity: "O(n). We visit each node exactly twice — once during in-order traversal to extract values, and once during reconstruction."
  space_complexity: "O(n). We store all `n` values in an array, plus O(log n) recursion stack depth for the balanced tree construction."

solutions:
  - approach_name: In-order Traversal + Rebuild
    is_optimal: true
    code: |
      class TreeNode:
          def __init__(self, val=0, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right

      def balance_bst(root: TreeNode) -> TreeNode:
          # Step 1: Extract all values via in-order traversal (produces sorted array)
          values = []

          def inorder(node):
              if not node:
                  return
              inorder(node.left)      # Visit left subtree
              values.append(node.val)  # Record current node
              inorder(node.right)     # Visit right subtree

          inorder(root)

          # Step 2: Build balanced BST from sorted array
          def build(left: int, right: int) -> TreeNode | None:
              if left > right:
                  return None  # Base case: empty range

              # Pick middle element as root for balance
              mid = (left + right) // 2
              node = TreeNode(values[mid])

              # Recursively build left and right subtrees
              node.left = build(left, mid - 1)
              node.right = build(mid + 1, right)

              return node

          return build(0, len(values) - 1)
    explanation: |
      **Time Complexity:** O(n) — In-order traversal visits each node once, and reconstruction visits each value once.

      **Space Complexity:** O(n) — The array stores all values. Recursion stack is O(log n) for the balanced tree.

      This solution elegantly combines two fundamental operations: BST in-order traversal (which guarantees sorted output) and divide-and-conquer tree construction (which guarantees balance). The middle element becomes the root at each level, ensuring subtrees have equal sizes.

  - approach_name: Iterative In-order + Rebuild
    is_optimal: false
    code: |
      def balance_bst(root: TreeNode) -> TreeNode:
          # Iterative in-order traversal using explicit stack
          values = []
          stack = []
          current = root

          while stack or current:
              # Go as far left as possible
              while current:
                  stack.append(current)
                  current = current.left

              # Process node and move right
              current = stack.pop()
              values.append(current.val)
              current = current.right

          # Build balanced BST (same as recursive approach)
          def build(left: int, right: int) -> TreeNode | None:
              if left > right:
                  return None

              mid = (left + right) // 2
              node = TreeNode(values[mid])
              node.left = build(left, mid - 1)
              node.right = build(mid + 1, right)
              return node

          return build(0, len(values) - 1)
    explanation: |
      **Time Complexity:** O(n) — Same as recursive approach.

      **Space Complexity:** O(n) — Array for values plus O(h) for the traversal stack where h is tree height.

      This variant uses iterative in-order traversal with an explicit stack, which can be useful if recursion depth is a concern for extremely unbalanced trees. The reconstruction phase remains the same.
