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   **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]`   **Step 3: Handle base case** - When `left > right`, the range is empty — return `None` - This terminates the recursion at leaf boundaries   **Step 4: Return the new root** - The initial call with the full range `[0, n - 1]` returns the root of the balanced BST   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.