DSA sheet · Trees · BST pattern

Convert Sorted Array to BST

This is the first problem of the BST pattern. The teacher uses it to introduce what a binary search tree is, and then shows how to build one from a sorted list of numbers. The main idea is pick the middle number as the root, then do the same for each half. It is the same "cut in half" thinking as binary search. You will also learn a habit that every tree-building problem needs: attach what the recursive call returns to root.left / root.right, and always return the node you made.

Every problem below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a tree node?

Each node is a small box with 3 things: its value, a link to its left child and a link to its right child. A missing child is None.

given by LeetCode, don't write this in the solution
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val       # the number in this node
        self.left = left     # left child, or None
        self.right = right   # right child, or None

What is a Binary Search Tree (BST)?

A BST is a normal binary tree with one extra rule about where numbers are allowed to sit:

The BST ruleFor every node:
• every value in its left subtree is smaller than the node,
• every value in its right subtree is bigger than the node.
"Subtree" means the child and everything below it, not just the child.
          5
        /   \
       2     8
      / \   / \
     1   4 6   9

Check node 5: its left side has 2, 1, 4, all smaller than 5 ✓. Its right side has 8, 6, 9, all bigger ✓. Now check node 2: left has 1 (smaller ✓), right has 4 (bigger ✓). The same rule must hold at 8, and at every other node.

Doubt: if every child is in the right place compared to its parent, is it a BST?
→ Not always. The teacher points this out with node 2. Values to the right of 2 must be bigger than 2, but they are still inside 5's left subtree, so they must also be smaller than 5. Look at this tree:
          5
        /   \
       2     8
        \
         7       ← 7 > 2, fine for its parent…
                  …but 7 > 5 and it sits on 5's LEFT side ✗

So 7 breaks the rule for 5. Every node's value must fit the limits set by all of its ancestors (parent, grandparent, and so on up to the root), not only its parent.

Why the inorder of a BST is sorted

Inorder traversal means: visit the left subtree, then the node, then the right subtree (and do the same inside each subtree). For a BST:

The same thing happens inside every subtree, so the full output comes out in increasing order. For the tree above, inorder gives 1 2 4 5 6 8 9.

This problem is that fact run backwards. We are given the sorted list, which is the inorder of the tree we want, and we must build the tree.

Height and "height balanced"

Part A · Sorted Array to BST with DFS

LeetCode 108 · Convert Sorted Array to Binary Search Tree

1The question in simple words

You get a list nums sorted in increasing order. Build a BST that holds all these numbers, and make it height balanced. Return its root.

Example: nums = [-10, -3, 0, 5, 9]. One correct answer:

          0
        /   \
     -10     5
        \     \
        -3     9

The question accepts any height-balanced BST, so more than one answer can be correct.

2What the constraints tell us

3Intuition: how to think about it

Many BSTs can be made from the same list

The teacher first shows that the root can be almost anything. Try a few roots with [-10, -3, 0, 5, 9]:

root = −10
-10
   \
   -3
     \
      0
       \
        5
         \
          9
root = −3
     -3
    /  \
 -10    0
         \
          5
           \
            9
root = 0 (middle)
       0
     /   \
   -3     5
   /       \
 -10        9

All three are valid BSTs. Each new number just goes left if it's smaller and right if it's bigger. But the question also wants height balanced, so most of these fail.

Take the tree with root −3. Its left side is just −10, so the left height is 1. Its right side is 0 → 5 → 9, so the right height is 3. The difference is 2, which is more than 1 → not balanced.

Doubt: in the video the difference is said to be 3. Is it 3 or 2?
→ It's 3 − 1 = 2. That was a slip while talking. It doesn't change the conclusion: anything above 1 means not balanced, so this tree is not allowed.

The fix: choose the middle

If the root is the middle number, half the numbers are smaller and half are bigger. Because the list is sorted:

The two halves have almost the same size, so the two sides get almost the same height. Then we do the same thing again for each half: take its middle as the root of that subtree, and so on. It's like binary search, but instead of throwing one half away, we build both halves.

  [-10, -3,  0,  5,  9]
             ↑ middle → root
  [-10, -3]     [5, 9]
   left half    right half
   → left        → right
     subtree       subtree

The BST rule is automatically true: every number that goes to the left half really is smaller than the root, because the list is sorted.

4Building the logic step by step

What does our function receive? Not a root!

In most tree problems the function gets a root. Here there is no tree yet. We are the ones creating it. What we do have is the list. So the function gets the list and two indexes: left and right, which mark the part of the list we still have to turn into a subtree.

Doubt: why not pass the smaller list itself, like nums[:mid]?
→ It would work, but every slice copies those numbers into a new list. That adds extra time and memory. Two indexes say the same thing ("work on this piece") with no copying. Indexes also give us a clean stopping rule, explained next.

Base case: when do we stop?

Recursion needs a condition that stops it. Here the indexes give us that condition:

What do we return for an empty piece? The caller will put our answer into root.left or root.right. Those slots can hold only a node or None. No node here, so return None.

Base caseif left > right: return None
The teacher's tip: whenever you're given a list (something with indexes) instead of a root, this "left crossed right" check is your stopping condition.
Doubt: can I write left >= right?
→ No. When left == right, one number is still waiting to become a node. With >= you would skip it and lose numbers (every leaf would be missing).

Find the middle index

mid = left + (right - left) // 2

Doubt: why not simply (left + right) // 2?
→ In Python both give the same answer. The teacher uses the longer form because in Java/C++, left + right can go past the int limit (about 10⁹) for huge arrays and overflow. right - left is always small, so it's the safe habit. Using it in Python is harmless and makes the code easy to move to other languages.
Doubt: with an even number of items there are two middles. Which one?
→ // rounds down, so we get the left one of the two. Picking the right one would also give a balanced tree. Both are accepted.

Make the root, then build both sides

Make a node from the middle number: root = TreeNode(nums[mid]). Now the two halves:

The list stays the same in every call. Only left and right change.

Doubt: why mid - 1 and mid + 1, and not mid?
→ nums[mid] is already used as the root. If we passed mid again, the same number would be added twice, and for a one-item piece the range would never shrink. The recursion would never end.

Return the root: this is when nodes get connected

After both sides are built, return root. The teacher stresses this point. When a call creates a node, that node is not yet attached to anything. It gets attached only when the call returns it and the parent stores it in its .left or .right.

Example from the dry run: the call that builds −3 makes the node, its two children are None, and then it returns −3. Only then does the call for −10 store it: (-10).right = -3. Without return root, the parent would receive None and the tree would fall apart.

Doubt: in some problems we write left = self.f(root.left) (a plain variable), and here we write root.left = self.build(...). How do I know which one?
→ The teacher's rule:
• When you are building or changing the tree's shape (constructing, inserting, deleting), the call returns a node, and you hook it into root.left / root.right.
• When you only read the tree (height, sum, checking something), you're not allowed to change its shape. You store the answer in a normal variable (a number, True/False) and combine.
This problem builds the tree, so we attach.

5Approach steps (the algorithm in plain English)

  1. Call build(0, len(nums) - 1) and return what it gives.
  2. Inside build(left, right): if left > right, the piece is empty → return None.
  3. Find mid = left + (right - left) // 2.
  4. Make root = TreeNode(nums[mid]).
  5. root.left = build(left, mid - 1) (the smaller half).
  6. root.right = build(mid + 1, right) (the bigger half).
  7. Return root to the parent so it gets connected.

6Code (Python)

Sorted Array to BST with DFS
class Solution:
    def sortedArrayToBST(self, nums):
        return self.build(nums, 0, len(nums) - 1)

    def build(self, nums, left, right):
        if left > right:                       # empty piece -> no node
            return None
        mid = left + (right - left) // 2       # middle index (safe form)
        root = TreeNode(nums[mid])             # middle number becomes the root
        root.left = self.build(nums, left, mid - 1)    # smaller half
        root.right = self.build(nums, mid + 1, right)  # bigger half
        return root                            # hand it to the parent

7Code line by line

linewhat it means
return self.build(nums, 0, len(nums) - 1)Build a tree from the whole list. The node it returns is the root of the full tree, and that is our answer.
if left > right: return NoneThe pointers crossed, so there are no numbers in this piece. Return None to fill the empty child slot. This is the base case that stops the recursion.
mid = left + (right - left) // 2The middle of the current piece. Overflow-safe form (matters in Java/C++).
root = TreeNode(nums[mid])Create the root of this subtree with the middle number. It isn't connected to anything yet.
root.left = self.build(nums, left, mid - 1)Python pauses here and builds the entire left half first (DFS). Whatever comes back is attached as the left child.
root.right = self.build(nums, mid + 1, right)Then build the right half and attach it as the right child.
return rootGive this finished subtree to the call that asked for it. This return is what connects nodes together.

8Dry run using the call stack

nums = [-10, -3, 0, 5, 9], indexes 0 to 4. b(l, r) means build(nums, l, r).

  1. b(0, 4): mid = 0 + 4//2 = 2 → make node 0. Go left first. (b(0,4) waits on the stack.)
  2. b(0, 1): mid = 0 + 1//2 = 0 → make node −10. Go left. (b(0,1) waits.)
  3. b(0, −1): left 0 > right −1 → crossed → return None. So (−10).left = None.
  4. Back in b(0,1), go right: b(1, 1): one number left, mid = 1 → make node −3.
  5. Inside b(1,1): left call b(1, 0) → crossed → None. Right call b(2, 1) → crossed → None. −3 is a leaf.
  6. b(1,1) returns −3. Now (−10).right = −3 gets connected.
  7. b(0,1) returns −10 (with −3 hanging on its right). Now (0).left = −10 gets connected.
  8. Back in b(0,4), go right: b(3, 4): mid = 3 + 1//2 = 3 → make node 5.
  9. b(3,4) left: b(3, 2) → crossed → None. Right: b(4, 4) → make 9, its children b(4,3) and b(5,4) are both None → returns 9. So (5).right = 9.
  10. b(3,4) returns 5 → (0).right = 5. b(0,4) returns 0, the root of the whole tree. Done ✓
stack at step 3
b(0,4) → 0b(0,1) → −10b(0,−1) → None
stack at step 5
b(0,4) → 0b(0,1) → −10b(1,1) → −3b(1,0) → None
stack at step 9
b(0,4) → 0b(3,4) → 5b(4,4) → 9

The newest call is on top (red). A node only joins its parent when its call returns and leaves the stack.

  final tree:      0
                 /   \
              -10     5
                 \     \
                 -3     9
  left height = 3 (0, -10, -3)   right height = 3 (0, 5, 9)   difference 0 ✓

Inorder of this tree: −10, −3, 0, 5, 9, exactly the input list ✓. So it's a correct BST, and it's balanced.

9Complexity & remember

Why not BFS here?

The teacher says DFS is the natural fit. BFS moves through a tree level by level. But this problem isn't about levels. The work at each node is "this number is the root, now send the smaller numbers to the left side and the bigger numbers to the right side". That is a split and recurse job, so recursion matches it directly.

Doubt: is it truly impossible with a queue?
→ Not impossible. You can put (node, left, right) triples in a queue and fill the children level by level. It just needs more bookkeeping and gives the same O(n) time. So the teacher's advice holds: DFS is the most suitable way. Here is the queue version only for curiosity:
optional: the same tree built with a queue
from collections import deque

class SolutionQueue:
    def sortedArrayToBST(self, nums):
        if not nums:
            return None
        mid = (len(nums) - 1) // 2
        root = TreeNode(nums[mid])
        queue = deque([(root, 0, len(nums) - 1)])   # node + the range it came from
        while queue:
            node, left, right = queue.popleft()
            mid = left + (right - left) // 2        # node's own index
            if left <= mid - 1:                     # numbers left for a left child
                m = left + (mid - 1 - left) // 2
                node.left = TreeNode(nums[m])
                queue.append((node.left, left, mid - 1))
            if mid + 1 <= right:                    # numbers left for a right child
                m = mid + 1 + (right - mid - 1) // 2
                node.right = TreeNode(nums[m])
                queue.append((node.right, mid + 1, right))
        return root
Remember Sorted Array → BST Middle = root · left part → root.left · right part → root.right · left > right → None · always return root. Time O(n), space O(log n).

Part B · Revision page

questionanswer
Why pick the middle?Equal halves on both sides → heights differ by at most 1 → height balanced.
Why does the BST rule hold automatically?The list is sorted, so the left part is smaller than the middle and the right part is bigger.
What does the function get?The list + left, right indexes (no root, we're creating it).
Base caseleft > right → None. (left == right still has one number.)
Left half / right half(left, mid-1) / (mid+1, right)
Where does the result of a call go?Into root.left / root.right (we're building the tree).
DFS or BFS?DFS: it's a split-and-recurse job, not a level-by-level one.
Time / spaceO(n) / O(log n)
Building / changing a treeOnly reading a tree
the call returnsa node (or None)a value (number, True/False)
store it inroot.left = … / root.right = …a normal variable, then combine
examplesthis problem, Insert into BST, Delete from BSTheight, diameter, Same Tree
If you remember only 5 lines 1. BST: everything on the left is smaller, everything on the right is bigger, at every node. Its inorder is sorted.
2. A sorted list is the inorder of the BST we want. The middle number is the root.
3. Recurse on (left, mid-1) for root.left and (mid+1, right) for root.right.
4. Stop when left > right and return None.
5. return root: nodes get connected only when calls return.
Mistakes to avoid ✗ base case left >= right (one-number pieces get lost)
✗ recursing with mid instead of mid-1 / mid+1 (duplicates, endless recursion)
✗ forgetting return root (parent gets None, tree breaks)
✗ saving the call into a plain variable instead of root.left / root.right
✗ picking the first number as root (valid BST, but not balanced)
✗ in Java/C++: (left + right) / 2 can overflow
test it yourself (paste under the solution above)
def inorder(node):
    return inorder(node.left) + [node.val] + inorder(node.right) if node else []

def height(node):
    return 1 + max(height(node.left), height(node.right)) if node else 0

s = Solution()
root = s.sortedArrayToBST([-10, -3, 0, 5, 9])
print(root.val, root.left.val, root.left.right.val, root.right.val, root.right.right.val)  # 0 -10 -3 5 9
print(inorder(root))                              # [-10, -3, 0, 5, 9]
print(height(root.left), height(root.right))      # 2 2
print(inorder(s.sortedArrayToBST([7])))           # [7]
print(inorder(s.sortedArrayToBST([1, 3])))        # [1, 3]

Based on this video: Convert Sorted Array to Binary Search Tree