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

function_signature: "def sorted_array_to_bst(nums: list[int]) -> TreeNode | None:"

test_cases:
  visible:
    - input: { nums: [-10, -3, 0, 5, 9] }
      expected: [0, -3, 9, -10, null, 5]
    - input: { nums: [1, 3] }
      expected: [3, 1]
  hidden:
    - input: { nums: [0] }
      expected: [0]
    - input: { nums: [1, 2, 3, 4, 5, 6, 7] }
      expected: [4, 2, 6, 1, 3, 5, 7]
    - input: { nums: [-5, -3, -1, 0, 2, 4, 6] }
      expected: [0, -3, 4, -5, -1, 2, 6]
    - input: { nums: [1, 2] }
      expected: [2, 1]
    - input: { nums: [1, 2, 3, 4, 5] }
      expected: [3, 2, 5, 1, null, 4]
    - input: { nums: [-10000, 0, 10000] }
      expected: [0, -10000, 10000]

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

    &nbsp;

    **Step 2: Base case**

    - If `left > right`, we've exhausted this subarray — return `None` (empty subtree)

    &nbsp;

    **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

    &nbsp;

    **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

    &nbsp;

    **Step 5: Return the root**

    - Return the constructed node, which connects upward to its parent (or is the final tree root)

    &nbsp;

    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.
