108. Convert Sorted Array to Binary Search Tree
Problem Description
You are given an integer array nums
that is sorted in ascending order. Your task is to convert this sorted array into a height-balanced binary search tree (BST).
A height-balanced binary search tree is a binary tree where the depth of the two subtrees of every node never differs by more than 1. This means the tree should be as balanced as possible - the left and right subtrees of any node should have heights that differ by at most 1.
Since the input array is already sorted, you need to construct a BST that:
- Maintains the BST property (left subtree values < root < right subtree values)
- Is height-balanced to ensure optimal performance for tree operations
The key insight is to choose the middle element of the array (or subarray) as the root node at each step. This ensures the tree remains balanced since roughly half the elements will go to the left subtree and half to the right subtree.
For example, if you have nums = [-10, -3, 0, 5, 9]
:
- Choose the middle element
0
as root - Elements to the left
[-10, -3]
form the left subtree - Elements to the right
[5, 9]
form the right subtree - Recursively apply this process to build the complete tree
The solution uses a divide-and-conquer approach with recursion, where at each step:
- Find the middle index:
mid = (left + right) // 2
- Create a node with
nums[mid]
as the value - Recursively build the left subtree from elements
[left, mid-1]
- Recursively build the right subtree from elements
[mid+1, right]
- Return the constructed node
Intuition
When we need to build a height-balanced BST from a sorted array, the main challenge is ensuring the tree remains balanced. If we simply insert elements one by one from the sorted array, we'd end up with a skewed tree (essentially a linked list), which defeats the purpose of using a BST.
The key observation is that in a balanced BST, the root node should have approximately the same number of nodes in its left and right subtrees. Since our array is already sorted, we know that:
- All elements to the left of any index are smaller
- All elements to the right of any index are larger
This naturally suggests choosing the middle element as the root. Why? Because this choice automatically divides the remaining elements into two roughly equal halves - perfect for creating balanced subtrees.
Think of it like organizing a tournament bracket. To make it fair and balanced, you'd want each side of the bracket to have roughly the same number of participants. Similarly, by always choosing the middle element as the "dividing point" (root), we ensure that both subtrees get approximately n/2
elements.
The recursive pattern emerges naturally:
- Pick the middle element as root (this becomes the "parent" in our tree)
- Everything to the left of middle becomes the left subtree (all smaller values)
- Everything to the right of middle becomes the right subtree (all larger values)
- Apply the same logic recursively to each half
This approach guarantees balance because at each level of recursion, we're dividing the problem size roughly in half. The height difference between any two subtrees can never exceed 1 because we're always splitting as evenly as possible.
The beauty of this solution is that it leverages the sorted property of the input - we don't need to sort or rearrange anything, just strategically pick our root nodes to maintain balance while preserving the BST property.
Solution Approach
The solution implements a divide-and-conquer approach using recursion. We create a helper function dfs(l, r)
that constructs a balanced BST from the subarray nums[l...r]
.
Implementation Details:
The recursive function dfs(l, r)
works as follows:
-
Base Case: If
l > r
, the subarray is empty, so we returnNone
(null node). -
Find the Middle Element: Calculate the middle index using
mid = (l + r) >> 1
. The bit shift operation>> 1
is equivalent to integer division by 2, giving usmid = (l + r) // 2
. -
Create Root Node: Create a new
TreeNode
with valuenums[mid]
. This middle element becomes the root of the current subtree. -
Recursively Build Subtrees:
- Left subtree: Call
dfs(l, mid - 1)
to construct the left subtree from elements in range[l, mid-1]
- Right subtree: Call
dfs(mid + 1, r)
to construct the right subtree from elements in range[mid+1, r]
- Left subtree: Call
-
Connect and Return: The
TreeNode
constructor is called with three arguments:TreeNode(nums[mid], dfs(l, mid - 1), dfs(mid + 1, r))
This creates the node with:
val = nums[mid]
(the node's value)left = dfs(l, mid - 1)
(left subtree)right = dfs(mid + 1, r)
(right subtree)
Initial Call: The process starts with dfs(0, len(nums) - 1)
, which considers the entire array.
Time Complexity: O(n)
where n
is the number of elements in the array. Each element is visited exactly once to create its corresponding node.
Space Complexity: O(log n)
for the recursion stack in a balanced tree (the height of the tree). The total space including the output tree is O(n)
.
Why This Works: By always selecting the middle element as the root, we ensure that the number of nodes in the left and right subtrees differs by at most 1. This maintains the height-balanced property throughout the tree construction. The sorted nature of the input array guarantees that the BST property (left < root < right) is automatically satisfied.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example: nums = [-10, -3, 0, 5, 9]
Initial Call: dfs(0, 4)
(processing indices 0 through 4)
Step 1: First recursive call dfs(0, 4)
- Calculate middle:
mid = (0 + 4) // 2 = 2
- Create root node with
nums[2] = 0
- Make recursive calls:
- Left:
dfs(0, 1)
for elements[-10, -3]
- Right:
dfs(3, 4)
for elements[5, 9]
- Left:
Step 2: Process left subtree dfs(0, 1)
- Calculate middle:
mid = (0 + 1) // 2 = 0
- Create node with
nums[0] = -10
- Make recursive calls:
- Left:
dfs(0, -1)
returnsNone
(base case: empty range) - Right:
dfs(1, 1)
for element[-3]
- Left:
Step 3: Process dfs(1, 1)
- Calculate middle:
mid = (1 + 1) // 2 = 1
- Create node with
nums[1] = -3
- Both
dfs(1, 0)
anddfs(2, 1)
returnNone
(empty ranges) - Return node(-3) with no children
Step 4: Complete left subtree
- Node(-10) gets right child = node(-3)
- Left subtree of root is complete
Step 5: Process right subtree dfs(3, 4)
- Calculate middle:
mid = (3 + 4) // 2 = 3
- Create node with
nums[3] = 5
- Make recursive calls:
- Left:
dfs(3, 2)
returnsNone
(empty range) - Right:
dfs(4, 4)
for element[9]
- Left:
Step 6: Process dfs(4, 4)
- Calculate middle:
mid = (4 + 4) // 2 = 4
- Create node with
nums[4] = 9
- Both recursive calls return
None
- Return node(9) with no children
Step 7: Complete right subtree
- Node(5) gets right child = node(9)
- Right subtree of root is complete
Final Tree Structure:
0 / \ -10 5 \ \ -3 9
The tree is height-balanced:
- Root's left subtree has height 2
- Root's right subtree has height 2
- All subtrees maintain the height difference ≤ 1 property
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
8from typing import List, Optional
9
10class Solution:
11 def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
12 """
13 Convert a sorted array to a height-balanced binary search tree.
14
15 Args:
16 nums: A sorted array in ascending order
17
18 Returns:
19 The root node of the height-balanced BST
20 """
21
22 def build_bst(left: int, right: int) -> Optional[TreeNode]:
23 """
24 Recursively build a balanced BST from array segment.
25
26 Args:
27 left: Starting index of the current segment
28 right: Ending index of the current segment
29
30 Returns:
31 Root node of the subtree built from nums[left:right+1]
32 """
33 # Base case: invalid range means no node to create
34 if left > right:
35 return None
36
37 # Choose middle element as root to ensure balance
38 # Using bit shift for efficient integer division by 2
39 mid = (left + right) >> 1
40
41 # Create root node with middle element
42 # Recursively build left subtree from left half
43 # Recursively build right subtree from right half
44 root = TreeNode(
45 val=nums[mid],
46 left=build_bst(left, mid - 1),
47 right=build_bst(mid + 1, right)
48 )
49
50 return root
51
52 # Start building BST with entire array range
53 return build_bst(0, len(nums) - 1)
54
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 // Global array to store the sorted input array
18 private int[] nums;
19
20 /**
21 * Converts a sorted array to a height-balanced Binary Search Tree.
22 * The strategy is to always choose the middle element as the root,
23 * which ensures the tree remains balanced.
24 *
25 * @param nums The sorted array to convert
26 * @return The root node of the constructed BST
27 */
28 public TreeNode sortedArrayToBST(int[] nums) {
29 // Store the array as instance variable for access in helper method
30 this.nums = nums;
31
32 // Build the tree recursively using the entire array range
33 return buildBST(0, nums.length - 1);
34 }
35
36 /**
37 * Recursively builds a BST from a sorted array segment.
38 * Uses divide-and-conquer approach by selecting the middle element as root,
39 * then recursively building left and right subtrees.
40 *
41 * @param left The left boundary index (inclusive)
42 * @param right The right boundary index (inclusive)
43 * @return The root node of the subtree, or null if range is invalid
44 */
45 private TreeNode buildBST(int left, int right) {
46 // Base case: invalid range means no nodes to create
47 if (left > right) {
48 return null;
49 }
50
51 // Calculate the middle index to maintain balance
52 // Using bit shift for division by 2 (equivalent to (left + right) / 2)
53 int middle = (left + right) >> 1;
54
55 // Create the root node with middle element value
56 // Recursively construct left subtree from left half
57 // Recursively construct right subtree from right half
58 return new TreeNode(
59 nums[middle],
60 buildBST(left, middle - 1),
61 buildBST(middle + 1, right)
62 );
63 }
64}
65
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 * Converts a sorted array to a height-balanced Binary Search Tree (BST).
16 * @param nums - A sorted array in ascending order
17 * @return The root node of the constructed BST
18 */
19 TreeNode* sortedArrayToBST(vector<int>& nums) {
20 // Use helper function to build the tree recursively
21 return buildBST(nums, 0, nums.size() - 1);
22 }
23
24private:
25 /**
26 * Helper function to recursively build a BST from a sorted array segment.
27 * Uses divide-and-conquer approach: middle element becomes root,
28 * left half forms left subtree, right half forms right subtree.
29 *
30 * @param nums - Reference to the sorted array
31 * @param left - Starting index of the current segment (inclusive)
32 * @param right - Ending index of the current segment (inclusive)
33 * @return Root node of the subtree constructed from nums[left...right]
34 */
35 TreeNode* buildBST(vector<int>& nums, int left, int right) {
36 // Base case: invalid range means no nodes to create
37 if (left > right) {
38 return nullptr;
39 }
40
41 // Calculate middle index to ensure balanced tree
42 // Using bit shift for efficiency: (left + right) / 2
43 int mid = (left + right) >> 1;
44
45 // Create root node with middle element
46 // Recursively build left subtree from nums[left...mid-1]
47 // Recursively build right subtree from nums[mid+1...right]
48 TreeNode* root = new TreeNode(
49 nums[mid],
50 buildBST(nums, left, mid - 1),
51 buildBST(nums, mid + 1, right)
52 );
53
54 return root;
55 }
56};
57
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 a sorted array to a height-balanced binary search tree.
17 * The strategy is to always choose the middle element as the root to ensure balance.
18 *
19 * @param nums - The sorted array in ascending order
20 * @returns The root node of the constructed BST, or null if the array is empty
21 */
22function sortedArrayToBST(nums: number[]): TreeNode | null {
23 /**
24 * Recursively builds a BST from a subarray defined by left and right indices.
25 * Selects the middle element as the root and recursively builds left and right subtrees.
26 *
27 * @param leftIndex - The starting index of the current subarray
28 * @param rightIndex - The ending index of the current subarray
29 * @returns The root node of the subtree, or null if indices are invalid
30 */
31 const buildBST = (leftIndex: number, rightIndex: number): TreeNode | null => {
32 // Base case: invalid range means no nodes to process
33 if (leftIndex > rightIndex) {
34 return null;
35 }
36
37 // Calculate the middle index using bit shift for integer division
38 const middleIndex: number = (leftIndex + rightIndex) >> 1;
39
40 // Create root node with middle element and recursively build subtrees
41 const rootNode: TreeNode = new TreeNode(
42 nums[middleIndex],
43 buildBST(leftIndex, middleIndex - 1), // Build left subtree
44 buildBST(middleIndex + 1, rightIndex) // Build right subtree
45 );
46
47 return rootNode;
48 };
49
50 // Start the recursive process with the entire array range
51 return buildBST(0, nums.length - 1);
52}
53
Time and Space Complexity
Time Complexity: O(n)
The algorithm visits each element in the input array exactly once to create a corresponding tree node. The recursive function dfs
is called once for each element in nums
:
- At each recursive call, we create one
TreeNode
which takesO(1)
time - Finding the middle index using
mid = (l + r) >> 1
isO(1)
- The total number of nodes created equals the length of the input array
n
- Therefore, the overall time complexity is
O(n)
Space Complexity: O(log n)
The space complexity consists of two components:
- Recursion call stack: Since we're building a balanced BST by always choosing the middle element as root, the height of the tree is
O(log n)
. The maximum depth of recursive calls equals the height of the tree, so the call stack usesO(log n)
space. - Output tree structure: While the created BST contains
n
nodes (which would beO(n)
space), this is typically not counted as part of the space complexity since it's the required output.
Therefore, considering only the auxiliary space used by the algorithm (the recursion stack), the space complexity is O(log n)
.
Common Pitfalls
1. Off-by-One Errors in Index Calculation
One of the most common mistakes is incorrectly handling the indices when making recursive calls, particularly mixing up inclusive vs. exclusive bounds.
Incorrect Implementation:
def build_bst(left, right):
if left >= right: # Wrong! This skips single-element arrays
return None
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = build_bst(left, mid) # Wrong! This includes mid again
root.right = build_bst(mid, right) # Wrong! This includes mid again
return root
Why it's wrong: Using left >= right
as the base case with inclusive bounds will terminate too early when left == right
, missing single-element subarrays. Additionally, including mid
in recursive calls creates duplicates.
Correct Implementation:
def build_bst(left, right):
if left > right: # Correct base case for inclusive bounds
return None
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = build_bst(left, mid - 1) # Exclude mid
root.right = build_bst(mid + 1, right) # Exclude mid
return root
2. Integer Overflow in Middle Calculation
While less common in Python due to arbitrary precision integers, in languages with fixed-size integers, calculating mid = (left + right) // 2
can cause overflow when left + right
exceeds the maximum integer value.
Problematic approach (in languages with fixed integers):
mid = (left + right) // 2 # Can overflow if left + right > MAX_INT
Safe alternatives:
# Method 1: Avoid overflow by reordering operations mid = left + (right - left) // 2 # Method 2: Using bit shift (shown in original solution) mid = (left + right) >> 1 # Still can overflow in some languages # Method 3: Most robust for languages with overflow concerns mid = left + ((right - left) >> 1)
3. Choosing Wrong Middle Element for Even-Length Arrays
When the array has an even number of elements, there are two possible "middle" elements. Consistently choosing one approach is important for predictable behavior.
Example with array [1, 2, 3, 4]:
# Lower middle approach (default integer division) mid = (0 + 3) // 2 = 1 # Chooses element at index 1 (value 2) # Upper middle approach mid = (0 + 3 + 1) // 2 = 2 # Chooses element at index 2 (value 3)
Both approaches create valid balanced BSTs, but they produce different tree structures. The solution should consistently use one approach. The standard approach (using integer division) picks the lower middle, which tends to create slightly more balanced trees when dealing with sequences of even-length subarrays.
4. Modifying the Original Array
Some developers might try to slice the array in each recursive call instead of passing indices:
Inefficient approach:
def sortedArrayToBST(self, nums):
if not nums:
return None
mid = len(nums) // 2
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums[:mid]) # Creates new array
root.right = self.sortedArrayToBST(nums[mid+1:]) # Creates new array
return root
Problems with this approach:
- Time complexity increases to O(n log n) due to array slicing
- Space complexity increases to O(n log n) for all the array copies
- Less efficient than index-based approach
Better approach: Use indices to track array segments without creating copies, as shown in the original solution.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
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
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!