title: Convert Sorted Array to Binary Search Tree slug: convert-sorted-array-to-binary-search-tree difficulty: easy leetcode_id: 108 leetcode_url: https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/ categories: - arrays - trees - recursion patterns: - binary-search - dfs description: | Given an integer array `nums` where the elements are sorted in **ascending order**, convert *it to a* ***height-balanced*** *binary search tree*. A **height-balanced** binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one. constraints: | - `1 <= nums.length <= 10^4` - `-10^4 <= nums[i] <= 10^4` - `nums` is sorted in a **strictly increasing** order examples: - input: "nums = [-10,-3,0,5,9]" output: "[0,-3,9,-10,null,5]" explanation: "[0,-10,5,null,-3,null,9] is also accepted. Both represent valid height-balanced BSTs." - input: "nums = [1,3]" output: "[3,1]" explanation: "[1,null,3] and [3,1] are both height-balanced BSTs." explanation: intuition: | Think of a sorted array as a flattened BST — specifically, the **inorder traversal** of a BST produces a sorted sequence. Our task is to reverse this process: reconstruct a balanced BST from its sorted elements. The key insight comes from how BSTs work: the **middle element** of a sorted array naturally becomes the **root** of a balanced tree. Why? Because half the elements are smaller (they go in the left subtree) and half are larger (they go in the right subtree). This equal distribution is exactly what makes a tree height-balanced. Imagine you're organising books on a balanced shelf. If you pick the middle book as your centre point, you'll have roughly the same number of books on each side. Now recursively apply this same logic to each side — pick the middle of the left half for the left shelf's centre, and the middle of the right half for the right shelf's centre. This "divide and conquer" approach naturally produces a balanced structure because at every level of recursion, we're splitting our remaining elements as evenly as possible. approach: | We solve this using a **Recursive Divide and Conquer** approach: **Step 1: Define the recursive function** - Create a helper function that takes the left and right boundaries of the current subarray - The function will return the root of the subtree built from elements in `nums[left..right]`   **Step 2: Base case** - If `left > right`, we've exhausted this subarray — return `None` (empty subtree)   **Step 3: Find the middle element** - Calculate `mid = (left + right) // 2` (or `left + (right - left) // 2` to avoid overflow in other languages) - The element at `nums[mid]` becomes the root of this subtree   **Step 4: Recursively build subtrees** - Left subtree: recursively process `nums[left..mid-1]` - Right subtree: recursively process `nums[mid+1..right]` - Attach these as the left and right children of the current root   **Step 5: Return the root** - Return the constructed node, which connects upward to its parent (or is the final tree root)   This approach guarantees a height-balanced tree because we always choose the middle element, ensuring each subtree has at most one more element than its sibling. common_pitfalls: - title: Off-by-One Errors in Boundaries description: | When setting up recursive boundaries, it's easy to make mistakes: - Left subtree should use `[left, mid - 1]`, not `[left, mid]` (which would include the root again) - Right subtree should use `[mid + 1, right]`, not `[mid, right]` Including the middle element in a subtree creates duplicates and infinite recursion. wrong_approach: "Using [left, mid] for left subtree" correct_approach: "Using [left, mid - 1] for left subtree" - title: Forgetting the Base Case description: | Without a proper base case (`left > right`), the recursion never terminates. This causes a stack overflow. The base case handles empty subarrays — when there are no elements left to process in a branch, we return `None` to represent an empty child. wrong_approach: "No base case or incorrect condition" correct_approach: "Return None when left > right" - title: Creating an Unbalanced Tree description: | If you don't choose the middle element as the root, you'll create an unbalanced tree. For example, always choosing the first element creates a right-skewed tree (like a linked list), which defeats the purpose of a BST for efficient operations. With `nums = [1, 2, 3, 4, 5]`, choosing `1` as root puts all other elements in the right subtree, giving height 4 instead of 2. wrong_approach: "Using first or last element as root" correct_approach: "Always use the middle element as root" key_takeaways: - "**Divide and conquer**: Split problems into smaller subproblems by choosing a pivot (middle element) and recursing on each half" - "**BST property from sorted input**: The middle of a sorted array is the natural root for a balanced BST" - "**Foundation for tree construction**: This technique extends to problems like converting sorted linked lists to BSTs, or building balanced trees from other traversals" - "**Logarithmic height guarantee**: By always splitting evenly, the tree height is `O(log n)`, enabling efficient search, insert, and delete operations" time_complexity: "O(n). We visit each element exactly once to create its corresponding tree node." space_complexity: "O(log n). The recursion stack depth equals the tree height, which is `O(log n)` for a balanced tree. The output tree uses O(n) space, but that's required by the problem." solutions: - approach_name: Recursive Divide and Conquer is_optimal: true code: | class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def sorted_array_to_bst(nums: list[int]) -> TreeNode | None: def build_tree(left: int, right: int) -> TreeNode | None: # Base case: no elements in this range if left > right: return None # Choose middle element as root for balance mid = (left + right) // 2 # Create node with middle element node = TreeNode(nums[mid]) # Recursively build left subtree from left half node.left = build_tree(left, mid - 1) # Recursively build right subtree from right half node.right = build_tree(mid + 1, right) return node return build_tree(0, len(nums) - 1) explanation: | **Time Complexity:** O(n) — Each element is visited exactly once. **Space Complexity:** O(log n) — Recursion stack depth for a balanced tree. We use the classic divide-and-conquer pattern: select the middle element as root, then recursively construct left and right subtrees from the remaining elements. This guarantees the tree is height-balanced. - approach_name: Iterative with Stack is_optimal: false code: | class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def sorted_array_to_bst(nums: list[int]) -> TreeNode | None: if not nums: return None # Stack holds tuples: (node, left_bound, right_bound, is_left_child, parent) # We'll process nodes and attach them to parents n = len(nums) mid = n // 2 root = TreeNode(nums[mid]) # Stack entries: (left, right, parent_node, is_left) stack = [] # Add left and right ranges to process if mid - 1 >= 0: stack.append((0, mid - 1, root, True)) if mid + 1 < n: stack.append((mid + 1, n - 1, root, False)) while stack: left, right, parent, is_left = stack.pop() if left > right: continue mid = (left + right) // 2 node = TreeNode(nums[mid]) # Attach to parent if is_left: parent.left = node else: parent.right = node # Add children ranges to stack if left <= mid - 1: stack.append((left, mid - 1, node, True)) if mid + 1 <= right: stack.append((mid + 1, right, node, False)) return root explanation: | **Time Complexity:** O(n) — Each element is processed once. **Space Complexity:** O(log n) — Stack depth mirrors tree height. This iterative approach simulates the recursion using an explicit stack. While it achieves the same result, the recursive solution is more intuitive and readable for tree problems. The iterative version is useful when recursion depth is a concern.