Before attempting this problem, you should be comfortable with:
Binary Search Trees (BST) - Understanding BST properties where left subtree values are less than root and right subtree values are greater
Recursion - Building solutions by breaking problems into smaller subproblems
Divide and Conquer - Splitting an array at the middle element to create balanced subtrees
1. Depth First Search
Intuition
To create a height-balanced BST from a sorted array, we need to ensure that for every node, the left and right subtrees have roughly equal heights. Since the array is sorted, the middle element should become the root. All elements before the middle go to the left subtree, and all elements after go to the right subtree. Applying this recursively builds a balanced tree.
Algorithm
Base case: If the array is empty, return null.
Find the middle index of the current array segment.
Create a new tree node with the middle element as its value.
Recursively build the left subtree using the left half of the array (elements before the middle).
Recursively build the right subtree using the right half of the array (elements after the middle).
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicTreeNodeSortedArrayToBST(int[] nums){if(nums.Length ==0){returnnull;}int mid = nums.Length /2;TreeNode root =newTreeNode(nums[mid]);
root.left =SortedArrayToBST(nums.Take(mid).ToArray());
root.right =SortedArrayToBST(nums.Skip(mid +1).ToArray());return root;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcsortedArrayToBST(nums []int)*TreeNode {iflen(nums)==0{returnnil}
mid :=len(nums)/2
root :=&TreeNode{Val: nums[mid]}
root.Left =sortedArrayToBST(nums[:mid])
root.Right =sortedArrayToBST(nums[mid+1:])return root
}
/**
* Definition for a binary tree node.
* class TreeNode(var `val`: Int = 0) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funsortedArrayToBST(nums: IntArray): TreeNode?{if(nums.isEmpty()){returnnull}val mid = nums.size /2val root =TreeNode(nums[mid])
root.left =sortedArrayToBST(nums.sliceArray(0 until mid))
root.right =sortedArrayToBST(nums.sliceArray(mid +1 until nums.size))return root
}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcsortedArrayToBST(_ nums:[Int])->TreeNode?{if nums.isEmpty {returnnil}let mid = nums.count /2let root =TreeNode(nums[mid])
root.left=sortedArrayToBST(Array(nums[0..<mid]))
root.right=sortedArrayToBST(Array(nums[(mid +1)...]))return root
}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
2. Depth First Search (Optimal)
Intuition
The previous approach creates new array copies for each recursive call, which is inefficient. Instead, we can pass indices (left and right boundaries) to indicate the current segment of the array. This avoids array slicing and reduces both time and space overhead while maintaining the same logic of choosing the middle element as root.
Algorithm
Define a helper function that takes left and right boundary indices.
Base case: If left > right, return null (empty segment).
Calculate the middle index as (left + right) / 2.
Create a tree node with the value at the middle index.
Recursively build the left subtree with boundaries (left, mid - 1).
Recursively build the right subtree with boundaries (mid + 1, right).
Call the helper with initial boundaries (0, n - 1) and return the result.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {number[]} nums
* @return {TreeNode}
*/sortedArrayToBST(nums){consthelper=(l, r)=>{if(l > r){returnnull;}const m = Math.floor((l + r)/2);const root =newTreeNode(nums[m]);
root.left =helper(l, m -1);
root.right =helper(m +1, r);return root;};returnhelper(0, nums.length -1);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicTreeNodeSortedArrayToBST(int[] nums){returnHelper(nums,0, nums.Length -1);}privateTreeNodeHelper(int[] nums,int l,int r){if(l > r){returnnull;}int m =(l + r)/2;TreeNode root =newTreeNode(nums[m]);
root.left =Helper(nums, l, m -1);
root.right =Helper(nums, m +1, r);return root;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcsortedArrayToBST(nums []int)*TreeNode {var helper func(l, r int)*TreeNode
helper =func(l, r int)*TreeNode {if l > r {returnnil}
m :=(l + r)/2
root :=&TreeNode{Val: nums[m]}
root.Left =helper(l, m-1)
root.Right =helper(m+1, r)return root
}returnhelper(0,len(nums)-1)}
/**
* Definition for a binary tree node.
* class TreeNode(var `val`: Int = 0) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funsortedArrayToBST(nums: IntArray): TreeNode?{funhelper(l: Int, r: Int): TreeNode?{if(l > r){returnnull}val m =(l + r)/2val root =TreeNode(nums[m])
root.left =helper(l, m -1)
root.right =helper(m +1, r)return root
}returnhelper(0, nums.size -1)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcsortedArrayToBST(_ nums:[Int])->TreeNode?{funchelper(_ l:Int,_ r:Int)->TreeNode?{if l > r {returnnil}let m =(l + r)/2let root =TreeNode(nums[m])
root.left=helper(l, m -1)
root.right=helper(m +1, r)return root
}returnhelper(0, nums.count -1)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity:
O(logn) space for recursion stack.
O(n) space for the output.
3. Iterative DFS
Intuition
The recursive approach can be converted to an iterative one using an explicit stack. We store pending work items on the stack, where each item contains a node to be filled and the array bounds it should use. This simulates the recursive call stack and processes each subtree systematically without recursion.
Algorithm
If the array is empty, return null.
Create a root node with a placeholder value.
Push the root node along with its bounds (0, n-1) onto a stack.
While the stack is not empty:
Pop an item containing the node and its bounds (l, r).
Calculate the middle index and set the node's value to nums[mid].
If there are elements to the left (l <= mid - 1), create a left child and push it with bounds (l, mid - 1).
If there are elements to the right (mid + 1 <= r), create a right child and push it with bounds (mid + 1, r).
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {number[]} nums
* @return {TreeNode}
*/sortedArrayToBST(nums){if(nums.length ===0){returnnull;}const root =newTreeNode(0);const stack =[[root,0, nums.length -1]];while(stack.length){const[node, l, r]= stack.pop();const m = Math.floor((l + r)/2);
node.val = nums[m];if(l <= m -1){
node.left =newTreeNode(0);
stack.push([node.left, l, m -1]);}if(m +1<= r){
node.right =newTreeNode(0);
stack.push([node.right, m +1, r]);}}return root;}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicTreeNodeSortedArrayToBST(int[] nums){if(nums.Length ==0){returnnull;}TreeNode root =newTreeNode(0);Stack<(TreeNode,int,int)> stack =newStack<(TreeNode,int,int)>();
stack.Push((root,0, nums.Length -1));while(stack.Count >0){var(node, l, r)= stack.Pop();int m =(l + r)/2;
node.val = nums[m];if(l <= m -1){
node.left =newTreeNode(0);
stack.Push((node.left, l, m -1));}if(m +1<= r){
node.right =newTreeNode(0);
stack.Push((node.right, m +1, r));}}return root;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcsortedArrayToBST(nums []int)*TreeNode {iflen(nums)==0{returnnil}type item struct{
node *TreeNode
l, r int}
root :=&TreeNode{Val:0}
stack :=[]item{{root,0,len(nums)-1}}forlen(stack)>0{
curr := stack[len(stack)-1]
stack = stack[:len(stack)-1]
m :=(curr.l + curr.r)/2
curr.node.Val = nums[m]if curr.l <= m-1{
curr.node.Left =&TreeNode{Val:0}
stack =append(stack, item{curr.node.Left, curr.l, m -1})}if m+1<= curr.r {
curr.node.Right =&TreeNode{Val:0}
stack =append(stack, item{curr.node.Right, m +1, curr.r})}}return root
}
/**
* Definition for a binary tree node.
* class TreeNode(var `val`: Int = 0) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funsortedArrayToBST(nums: IntArray): TreeNode?{if(nums.isEmpty()){returnnull}val root =TreeNode(0)val stack = ArrayDeque<Triple<TreeNode, Int, Int>>()
stack.addLast(Triple(root,0, nums.size -1))while(stack.isNotEmpty()){val(node, l, r)= stack.removeLast()val m =(l + r)/2
node.`val` = nums[m]if(l <= m -1){
node.left =TreeNode(0)
stack.addLast(Triple(node.left!!, l, m -1))}if(m +1<= r){
node.right =TreeNode(0)
stack.addLast(Triple(node.right!!, m +1, r))}}return root
}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcsortedArrayToBST(_ nums:[Int])->TreeNode?{if nums.isEmpty {returnnil}let root =TreeNode(0)var stack:[(TreeNode,Int,Int)]=[(root,0, nums.count -1)]while!stack.isEmpty {let(node, l, r)= stack.removeLast()let m =(l + r)/2
node.val = nums[m]if l <= m -1{
node.left=TreeNode(0)
stack.append((node.left!, l, m -1))}if m +1<= r {
node.right=TreeNode(0)
stack.append((node.right!, m +1, r))}}return root
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity:
O(logn) space for the stack.
O(n) space for the output.
Common Pitfalls
Off-by-One Errors in Subarray Bounds
When recursively building subtrees, a common mistake is including the middle element in one of the subarrays, leading to infinite recursion or duplicate nodes.
# Wrong - includes mid in left subtree
root.left = helper(l, m)# should be m - 1# Correct
root.left = helper(l, m -1)
root.right = helper(m +1, r)
Choosing Wrong Middle Element in Even-Length Arrays
For arrays with even length, the middle can be either (l + r) // 2 or (l + r + 1) // 2. While both produce valid height-balanced BSTs, inconsistency can cause confusion. The problem accepts either choice, but stick with one consistently.