Free lesson · DSA Mastery - Interview Prep, End to End
Convert Sorted List to Binary Search Tree
Convert Sorted List to Binary Search Tree
Convert Sorted List to Binary Search Tree applies divide and conquer to tree construction: turn a sorted linked list into a height-balanced binary search tree. The divide step picks the middle element as the root, so the elements before it become a balanced left subtree and those after become a balanced right subtree. Here the split point - the midpoint - defines the tree's shape, and balance falls out of the divide strategy itself: always rooting at the middle guarantees the two sides differ in size by at most one at every level. We present three approaches: the simple "convert to array first" version, the slow/fast-pointer version that works on the list directly, and the elegant inorder-simulation version that builds the tree in a single O(n) pass.
The insight: a sorted sequence becomes a balanced BST when you repeatedly choose the middle as the root. The middle splits the sequence into two halves of nearly equal size, and applying the same rule recursively to each half keeps every subtree balanced - the BST ordering is automatic because the input is already sorted.
Interactive animation: Step through every approach yourself, frame by frame, on the lesson's own examples.
The Problem
Interactive animation: The example by hand: [-10, -3, 0, 5, 9] becomes a height-balanced tree rooted at 0.
Given the head of a singly linked list where 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.
Example 1:
Input: head = [-10,-3,0,5,9]
Output: [0,-3,9,-10,null,5] (one valid height-balanced BST)
Explanation: 0 is chosen as the root (middle element); [-10,-3] forms the left subtree,
[5,9] forms the right subtree.
Example 2:
Input: head = []
Output: []
Constraints:
- The number of nodes in
headis in the range[0, 2 * 10^4]. -10^5 <= Node.val <= 10^5
Approaches Overview
| # | Approach | Time | Space | Verdict |
|---|---|---|---|---|
| 1 | Copy to array, then build | O(n) | O(n) array | Simplest |
| 2 | Slow/fast pointer to find middle | O(n log n) | O(log n) stack | Works on the list directly |
| 3 | Inorder simulation (build in one pass) | O(n) | O(log n) stack | Optimal - clever combine |
Copying to an array makes midpoint access O(1) and gives clean O(n). The slow/fast approach avoids the array but re-walks to find each midpoint, costing O(n log n). The inorder simulation builds the tree left-to-right in sync with one list traversal - O(n) time and no array.
Approach 1: Copy to Array, Then Build
Interactive animation: Copy to an array, then root every range at its middle index: each node is drawn under the value it came from.
The Idea
Walk the list once into an array (random access). Then recursively build: the middle index is the root, the left half builds the left subtree, the right half builds the right subtree. Midpoint access is O(1), so the build is O(n) overall.
Logic Flowchart
graph TD
A["copy list values into array a[]"] --> B["build(lo, hi)"]
B --> C{"lo > hi?"}
C -->|Yes| D["return null"]
C -->|No| E["mid = (lo+hi)/2; root = a[mid]"]
E --> F["root.left = build(lo, mid-1)"]
F --> G["root.right = build(mid+1, hi)"]
G --> H["return root"]
style A fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style B fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style C fill:#b45309,stroke:#f59e0b,color:#f1f5f9
style D fill:#16a34a,stroke:#10b981,color:#f1f5f9
style E fill:#16a34a,stroke:#10b981,color:#f1f5f9
style F fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style G fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style H fill:#16a34a,stroke:#10b981,color:#f1f5f9
This flowchart shows the array approach. We copy the list into an array for random access, then recursively make the middle element the root and build the left and right subtrees from the two halves.
Code
public static TreeNode sortedListToBSTArray(ListNode head) {
List<Integer> vals = new ArrayList<>();
for (ListNode c = head; c != null; c = c.next) vals.add(c.val);
return build(vals, 0, vals.size() - 1);
}
private static TreeNode build(List<Integer> a, int lo, int hi) {
if (lo > hi) return null; // empty range
int mid = lo + (hi - lo) / 2;
TreeNode root = new TreeNode(a.get(mid)); // middle is the root
root.left = build(a, lo, mid - 1); // left half -> left subtree
root.right = build(a, mid + 1, hi); // right half -> right subtree
return root;
}
// TreeNode: int val; TreeNode left, right;
// Usage: sortedListToBSTArray([-10,-3,0,5,9]) -> balanced BST rooted at 0
This Java code copies the list into an array, then recursively builds a balanced tree by taking the middle element as the root and constructing the left and right subtrees from the surrounding halves.
Complexity
Time: O(n) - one copy pass plus O(1) midpoint per node. Space: O(n) for the array plus O(log n) recursion.
Approach 2: Slow/Fast Pointer to Find Middle
Interactive animation: Slow and fast walk to the middle of every range again. On even lengths they stop on the upper middle, so this tree is a different, equally balanced one.
The Idea
Avoid the array by finding the middle of the (sub)list with slow/fast pointers each time: slow advances one step, fast two, so slow lands on the middle. That node becomes the root; recurse on the list before it and the list after it. Finding the middle is O(k) for a length-k sublist, and there are log n levels of O(n) total work → O(n log n).
Logic Flowchart
graph TD
A["build(head, tail)"] --> B{"head == tail?"}
B -->|Yes| C["return null (empty range)"]
B -->|No| D["slow/fast from head to find mid"]
D --> E["root = new TreeNode(mid.val)"]
E --> F["root.left = build(head, mid)"]
F --> G["root.right = build(mid.next, tail)"]
G --> H["return root"]
style A fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style B fill:#b45309,stroke:#f59e0b,color:#f1f5f9
style C fill:#16a34a,stroke:#10b981,color:#f1f5f9
style D fill:#b45309,stroke:#f59e0b,color:#f1f5f9
style E fill:#16a34a,stroke:#10b981,color:#f1f5f9
style F fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style G fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style H fill:#16a34a,stroke:#10b981,color:#f1f5f9
This flowchart shows the slow-fast approach. For each sublist, slow and fast pointers locate the middle node, which becomes the root; the sublists before and after it build the left and right subtrees.
Code
public static TreeNode sortedListToBST(ListNode head) {
return build(head, null);
}
// build a BST from the half-open list range [head, tail)
private static TreeNode build(ListNode head, ListNode tail) {
if (head == tail) return null; // empty range
ListNode slow = head, fast = head;
while (fast != tail && fast.next != tail) { // find middle of [head, tail)
slow = slow.next;
fast = fast.next.next;
}
TreeNode root = new TreeNode(slow.val); // middle node is the root
root.left = build(head, slow); // nodes before middle
root.right = build(slow.next, tail); // nodes after middle
return root;
}
This Java code finds the middle of each half-open list range with slow and fast pointers, makes it the root, and recursively builds the left subtree from the nodes before it and the right subtree from the nodes after it.
Complexity
Time: O(n log n) - finding each midpoint re-walks the sublist. Space: O(log n) recursion stack.
Approach 3: Inorder Simulation (Build in One Pass) - Optimal
Interactive animation: The slots are known from n alone, then filled in list order as cur walks the list once.
The Idea
The clever observation: an inorder traversal of a BST visits nodes in sorted order - exactly the order of the list. So build the tree in the same order you consume the list. First count n. Then recursively build by index range [lo, hi]: build the left subtree first (consuming the smaller values), then take the current list node as the root and advance the list pointer, then build the right subtree. Because construction follows inorder, each list node is consumed exactly once, in order - O(n) with no array and no repeated midpoint scans.
Logic Flowchart
graph TD
A["count n; cur = head"] --> B["build(lo, hi)"]
B --> C{"lo > hi?"}
C -->|Yes| D["return null"]
C -->|No| E["mid = (lo+hi)/2"]
E --> F["left = build(lo, mid-1) (consume smaller values first)"]
F --> G["root = new TreeNode(cur.val); cur = cur.next"]
G --> H["root.left = left; root.right = build(mid+1, hi)"]
H --> I["return root"]
style A fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style B fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style C fill:#b45309,stroke:#f59e0b,color:#f1f5f9
style D fill:#16a34a,stroke:#10b981,color:#f1f5f9
style E fill:#16a34a,stroke:#10b981,color:#f1f5f9
style F fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style G fill:#16a34a,stroke:#10b981,color:#f1f5f9
style H fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style I fill:#16a34a,stroke:#10b981,color:#f1f5f9
This flowchart shows the inorder simulation. We build the left subtree first, then consume the current list node as the root and advance the pointer, then build the right subtree. Because construction follows inorder, list nodes are consumed in sorted order in a single pass.
Code
public class ListToBST {
private ListNode cur;
public TreeNode sortedListToBST(ListNode head) {
int n = 0;
for (ListNode c = head; c != null; c = c.next) n++;
cur = head;
return build(0, n - 1);
}
private TreeNode build(int lo, int hi) {
if (lo > hi) return null;
int mid = lo + (hi - lo) / 2;
TreeNode left = build(lo, mid - 1); // build left subtree first (inorder)
TreeNode root = new TreeNode(cur.val); // current node is this root
cur = cur.next; // advance list in sorted order
root.left = left;
root.right = build(mid + 1, hi); // then build right subtree
return root;
}
}
// Usage:
// new ListToBST().sortedListToBST([-10,-3,0,5,9]) -> balanced BST
This Java code counts the list length, then builds the tree by index range. It constructs the left subtree first, takes the current list node as the root and advances the pointer, then builds the right subtree, so the list is consumed in sorted order in one pass.
Step-by-Step Walkthrough
A frame-by-frame trace on head = [-10,-3,0,5,9] (n=5, indices 0–4):
graph LR
F0["build(0,4) mid2: build left(0,1) first"]
F1["left(0,1) mid0: left(0,-1)=null; root=cur(-10) adv; right(1,1)"]
F2["right(1,1) mid1: root=cur(-3) adv → so left subtree root -10 with right child -3"]
F3["back: root=cur(0) adv (the middle value); right(3,4): root 5, right child 9"]
F4["balanced BST rooted at 0"]
F0 --> F1 --> F2 --> F3 --> F4
style F0 fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style F1 fill:#4338ca,stroke:#6366f1,color:#f1f5f9
style F2 fill:#16a34a,stroke:#10b981,color:#f1f5f9
style F3 fill:#16a34a,stroke:#10b981,color:#f1f5f9
style F4 fill:#16a34a,stroke:#10b981,color:#f1f5f9
This walkthrough builds a balanced tree from minus ten, minus three, zero, five, nine. Construction follows inorder: the left subtree consumes minus ten and minus three, then zero becomes the root, then the right subtree consumes five and nine, all in a single sorted pass through the list.
Result: a height-balanced BST rooted at 0.
Complexity
Time: O(n) - one count pass plus one construction pass. Space: O(log n) recursion stack (balanced tree depth).
Complexity Comparison
| Approach | Time | Space | Key Idea | When to Use |
|---|---|---|---|---|
| Copy to array | O(n) | O(n) | O(1) midpoint via index | Simplest to write |
| Slow/fast pointer | O(n log n) | O(log n) | Find middle each level | No array, but re-walks |
| Inorder simulation | O(n) | O(log n) | Build in inorder = list order | Optimal time and space |
All three pick the middle as the root, so all produce a height-balanced BST. The array version is the easiest; the inorder simulation is the most elegant - it recognizes that BST construction can follow the list's natural order, achieving O(n) without an array. The slow/fast version is a nice "work on the list directly" middle ground.
Interactive animation: All three approaches on the example: the list steps each one takes, side by side.
Variations & Follow-ups
Why does choosing the middle as the root guarantee balance?
The middle splits a size-k range into halves of size ⌊(k-1)/2⌋ and ⌈(k-1)/2⌉, which differ by at most one. Applying this recursively, every node's two subtrees differ in size by at most one, and the height is O(log n). Picking any other element as root could make one side much larger, breaking the height-balance guarantee.
Interactive animation: Count what lands on each side of the middle: the two halves never differ by more than one.
Why is the inorder simulation O(n) when the slow/fast version is O(n log n)?
The slow/fast version re-walks each sublist to find its midpoint, repeating work across the log n levels → O(n log n). The inorder simulation never searches for a midpoint: it walks the list exactly once, consuming each node as the recursion reaches its inorder position. No repeated traversal means linear time.
Interactive animation: One cheap call per node: building from index ranges costs O(n).
Interactive animation: The slow/fast version walks to every middle again, about n list steps per level over log n levels.
How does the inorder trick work conceptually?
A BST's inorder traversal yields sorted values, and the list is already sorted - so the order in which nodes should be assigned values is exactly the list order. By building the left subtree before reading the current node and the right subtree after, the recursion visits "slots" in inorder, letting you fill them straight from the list pointer.
If the input were a sorted array instead of a list, which approach is best?
The array gives O(1) midpoint access, so the direct "middle as root" recursion (Approach 1 without the copy step) is already O(n) and simplest - that's the classic "Convert Sorted Array to BST" problem. The list version is harder precisely because a list has no random access, which is what motivates the slow/fast and inorder techniques.
What about edge cases?
- Empty list (Example 2): Returns null.
- Single node: Becomes a single-node tree (it's the middle).
- Two nodes: One becomes the root, the other a child - still height-balanced (heights differ by one).
- Even length:
lo + (hi-lo)/2picks the lower-middle; any consistent choice yields a valid balanced tree.
Common Interview Pitfalls
- Re-finding the middle by re-walking (then claiming O(n)). That's O(n log n); only the array or inorder approach is truly linear.
- Off-by-one in the list range. Using a half-open
[head, tail)range avoids confusion about which node is excluded. - Forgetting to advance the list pointer in the inorder version. The
cur = cur.nextafter reading the root is essential. - Producing a valid BST but not balanced. You must root at the middle; rooting at the head builds a degenerate (linked-list-shaped) tree.
Why This Topic Matters
Convert Sorted List to BST shows divide and conquer where the split point defines the output structure - choosing the middle as the root is what makes the tree balanced. It also showcases a beautiful combine optimization (the inorder simulation) that turns O(n log n) into O(n) by aligning construction order with the data's natural order. Both ideas - split to shape the result, and align traversal order with construction - recur throughout tree algorithms.
The next topic, Construct Quad Tree, takes divide and conquer into two dimensions with a four-way split: represent a grid as a quad tree by recursively dividing it into four quadrants. If a region is uniform (all 0s or all 1s) it becomes a leaf; otherwise it becomes an internal node with four children, one per quadrant, each built recursively. The combine step even merges four uniform leaves of the same value back into a single leaf - a divide-and-conquer with a four-way branch and a merging combine.