← Back to DevBytes

Solving Convert Sorted Array to BST in Go: Step-by-Step Guide

Introduction to Convert Sorted Array to BST

The "Convert Sorted Array to Binary Search Tree" problem is a classic algorithmic challenge frequently encountered in coding interviews and competitive programming. Given an integer array sorted in ascending order, the task is to construct a height-balanced binary search tree (BST). A height-balanced BST is one where the depth of the two subtrees of every node never differs by more than one.

This problem elegantly combines two fundamental computer science concepts: binary search and tree construction. Because the input array is already sorted, we can leverage the properties of binary search to pick the middle element as the root, ensuring the left and right subtrees contain roughly equal numbers of nodes. This guarantees the resulting tree is height-balanced.

Why It Matters

Understanding how to convert a sorted array into a BST is important for several reasons. First, it demonstrates mastery of recursion and divide-and-conquer strategies, which are essential techniques for solving complex problems efficiently. Second, balanced BSTs provide O(log n) time complexity for search, insertion, and deletion operations, making them valuable data structures for databases, file systems, and indexing mechanisms.

In real-world applications, you might encounter this scenario when migrating sorted data from a flat storage format into a tree-based index, or when building balanced search structures from pre-sorted datasets. Mastering this problem also builds intuition for related challenges, such as converting sorted linked lists to BSTs or balancing existing binary trees.

Understanding the Approach

The Core Idea

The key insight is that the middle element of a sorted array naturally becomes the root of a balanced BST. All elements to the left of the middle are smaller and belong in the left subtree, while all elements to the right are larger and belong in the right subtree. We apply this logic recursively to build the entire tree.

Step-by-Step Algorithm

Implementing the Solution in Go

Defining the Tree Node

First, we need to define the structure for a binary tree node. In Go, we use a struct with a value and pointers to left and right children.

package main

import "fmt"

// TreeNode represents a node in the binary search tree.
type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

The Recursive Function

Next, we implement the core recursive function that builds the BST from a subarray defined by start and end indices. Using indices rather than slicing the array avoids unnecessary memory allocations and keeps the time complexity at O(n).

// sortedArrayToBST converts a sorted array into a height-balanced BST.
func sortedArrayToBST(nums []int) *TreeNode {
    return buildBST(nums, 0, len(nums)-1)
}

// buildBST recursively constructs a BST from the subarray nums[left..right].
func buildBST(nums []int, left, right int) *TreeNode {
    // Base case: no elements to process
    if left > right {
        return nil
    }

    // Find the middle index. Using left + (right-left)/2 avoids
    // potential integer overflow compared to (left+right)/2.
    mid := left + (right-left)/2

    // Create the root node with the middle element
    root := &TreeNode{Val: nums[mid]}

    // Recursively build left and right subtrees
    root.Left = buildBST(nums, left, mid-1)
    root.Right = buildBST(nums, mid+1, right)

    return root
}

Helper Function to Verify the Tree

To confirm our solution works correctly, let us write a helper function that performs an in-order traversal. For a valid BST built from a sorted array, the in-order traversal should reproduce the original sorted array.

// inOrderTraversal prints the tree using in-order traversal.
func inOrderTraversal(root *TreeNode) []int {
    result := []int{}
    var traverse func(node *TreeNode)
    traverse = func(node *TreeNode) {
        if node == nil {
            return
        }
        traverse(node.Left)
        result = append(result, node.Val)
        traverse(node.Right)
    }
    traverse(root)
    return result
}

// treeHeight calculates the height of the tree.
func treeHeight(root *TreeNode) int {
    if root == nil {
        return 0
    }
    leftHeight := treeHeight(root.Left)
    rightHeight := treeHeight(root.Right)
    if leftHeight > rightHeight {
        return leftHeight + 1
    }
    return rightHeight + 1
}

// isBalanced checks whether the tree is height-balanced.
func isBalanced(root *TreeNode) bool {
    var check func(node *TreeNode) (int, bool)
    check = func(node *TreeNode) (int, bool) {
        if node == nil {
            return 0, true
        }
        leftHeight, leftBalanced := check(node.Left)
        rightHeight, rightBalanced := check(node.Right)
        balanced := leftBalanced && rightBalanced
        diff := leftHeight - rightHeight
        if diff < 0 {
            diff = -diff
        }
        if diff > 1 {
            balanced = false
        }
        height := leftHeight
        if rightHeight > height {
            height = rightHeight
        }
        return height + 1, balanced
    }
    _, balanced := check(root)
    return balanced
}

Putting It All Together

Now let us write the main function to test our implementation with a sample sorted array.

func main() {
    nums := []int{-10, -3, 0, 5, 9}

    root := sortedArrayToBST(nums)

    // Verify in-order traversal reproduces the sorted array
    traversal := inOrderTraversal(root)
    fmt.Println("In-order traversal:", traversal)
    // Output: In-order traversal: [-10 -3 0 5 9]

    // Verify the tree is height-balanced
    fmt.Println("Is balanced:", isBalanced(root))
    // Output: Is balanced: true

    // Print the tree height
    fmt.Println("Tree height:", treeHeight(root))
    // Output: Tree height: 3

    // Test with an empty array
    emptyRoot := sortedArrayToBST([]int{})
    fmt.Println("Empty tree root:", emptyRoot)
    // Output: Empty tree root: <nil>

    // Test with a single element
    singleRoot := sortedArrayToBST([]int{42})
    fmt.Println("Single element in-order:", inOrderTraversal(singleRoot))
    // Output: Single element in-order: [42]
}

Complexity Analysis

Understanding the time and space complexity of this solution is crucial for evaluating its efficiency.

Best Practices

Use Index-Based Recursion

Avoid slicing the array at each recursive step. While nums[mid+1:] might look cleaner, it creates new slices and adds overhead. Passing indices keeps memory usage minimal and improves performance, especially for large arrays.

Prevent Integer Overflow

When calculating the middle index, use left + (right-left)/2 instead of (left+right)/2. Although Go handles integer overflow differently than some languages, this habit is a good defensive programming practice that translates well across languages.

Handle Edge Cases

Always consider edge cases such as empty arrays, single-element arrays, and arrays with duplicate values. The base case left > right naturally handles empty and single-element scenarios, but you should test these explicitly.

Verify with Traversals

After constructing the tree, verify correctness using in-order traversal, which should reproduce the original sorted array. Additionally, check that the tree is height-balanced using a dedicated balance-checking function.

Consider Iterative Alternatives

For extremely large arrays where recursion depth might be a concern, consider an iterative approach using an explicit stack. While the recursive solution is more readable and idiomatic in Go, the iterative version eliminates recursion depth limitations entirely.

// sortedArrayToBSTIterative builds a BST iteratively using a stack.
func sortedArrayToBSTIterative(nums []int) *TreeNode {
    if len(nums) == 0 {
        return nil
    }

    type frame struct {
        node   *TreeNode
        left   int
        right  int
        isLeft bool
    }

    mid := (len(nums) - 1) / 2
    root := &TreeNode{Val: nums[mid]}
    stack := []frame{
        {node: root, left: 0, right: mid - 1, isLeft: true},
        {node: root, left: mid + 1, right: len(nums) - 1, isLeft: false},
    }

    for len(stack) > 0 {
        f := stack[len(stack)-1]
        stack = stack[:len(stack)-1]

        if f.left > f.right {
            continue
        }

        m := f.left + (f.right-f.left)/2
        child := &TreeNode{Val: nums[m]}

        if f.isLeft {
            f.node.Left = child
        } else {
            f.node.Right = child
        }

        stack = append(stack, frame{node: child, left: f.left, right: m - 1, isLeft: true})
        stack = append(stack, frame{node: child, left: m + 1, right: f.right, isLeft: false})
    }

    return root
}

Common Pitfalls to Avoid

Conclusion

Converting a sorted array into a height-balanced binary search tree is a foundational problem that reinforces essential concepts in recursion, divide-and-conquer, and tree data structures. By selecting the middle element as the root at each step and recursively processing the left and right halves, we achieve an elegant O(n) solution that produces a balanced tree. The Go implementation demonstrates how clean and readable this algorithm can be, while best practices such as index-based recursion, overflow-safe midpoint calculation, and thorough edge-case testing ensure the solution is both efficient and robust. Whether you are preparing for coding interviews or building real-world indexing systems, mastering this technique provides a solid foundation for tackling more advanced tree manipulation problems.

— Ad —

Google AdSense will appear here after approval

← Back to all articles