Facebook Pixel

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:

  1. Maintains the BST property (left subtree values < root < right subtree values)
  2. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Pick the middle element as root (this becomes the "parent" in our tree)
  2. Everything to the left of middle becomes the left subtree (all smaller values)
  3. Everything to the right of middle becomes the right subtree (all larger values)
  4. 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:

  1. Base Case: If l > r, the subarray is empty, so we return None (null node).

  2. 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 us mid = (l + r) // 2.

  3. Create Root Node: Create a new TreeNode with value nums[mid]. This middle element becomes the root of the current subtree.

  4. 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]
  5. 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 Evaluator

Example 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]

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) returns None (base case: empty range)
    • Right: dfs(1, 1) for element [-3]

Step 3: Process dfs(1, 1)

  • Calculate middle: mid = (1 + 1) // 2 = 1
  • Create node with nums[1] = -3
  • Both dfs(1, 0) and dfs(2, 1) return None (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) returns None (empty range)
    • Right: dfs(4, 4) for element [9]

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 takes O(1) time
  • Finding the middle index using mid = (l + r) >> 1 is O(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:

  1. 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 uses O(log n) space.
  2. Output tree structure: While the created BST contains n nodes (which would be O(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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

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

Load More