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:
- Take an existing BST (which may be unbalanced)
- Reorganize its structure to make it balanced
- Keep all the same values from the original tree
- 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:
- An in-order DFS traversal (
dfs
function) to extract all values from the original BST in sorted order - 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
Intuition
The key insight comes from understanding two fundamental properties:
- BST Property: An in-order traversal of a BST always gives us values in sorted order
- 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:
- Extract: Use DFS in-order traversal to get all values in sorted order (essentially "flattening" the unbalanced tree)
- 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:
- Base Case: If
i > j
, the range is empty, returnNone
- Find Middle: Calculate
mid = (i + j) >> 1
(bit shift for integer division) - 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
- Build left subtree from range
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 value1
- Right child:
build(2, 2)
→ creates node with value3
- Root =
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 EvaluatorExample 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 withnums[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 withnums[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:
- In-order traversal (dfs function): This visits each node exactly once to collect all values in sorted order, taking
O(n)
time. - 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, takingO(n)
time.
Therefore, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The space usage includes:
- nums array: Stores all
n
node values, requiringO(n)
space. - Recursion stack for dfs: In the worst case (skewed tree), the recursion depth can be
O(n)
. - Recursion stack for build: Since we're building a balanced tree, the maximum recursion depth is
O(log n)
. - New tree nodes: We create
n
new TreeNode objects, requiringO(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)
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Want a Structured Path to Master System Design Too? Don’t Miss This!