109. Convert Sorted List to Binary Search Tree
Problem Description
You are given the head
of a singly linked list where elements are sorted in ascending order. Your task is to convert this sorted linked list into a height-balanced binary search tree (BST).
A height-balanced binary search tree is a binary tree where:
- The left subtree of every node contains only nodes with values less than the node's value
- The right subtree of every node contains only nodes with values greater than the node's value
- The depth of the two subtrees of every node never differs by more than 1
For example, if you have a sorted linked list like 1 -> 2 -> 3 -> 4 -> 5
, you need to construct a balanced BST where the middle element becomes the root, and the elements are distributed evenly between left and right subtrees to maintain balance.
The solution approach converts the linked list to an array first, making it easier to access middle elements. Then it uses a divide-and-conquer strategy with depth-first search (DFS) to build the tree. The function dfs(i, j)
recursively:
- Finds the middle index
mid
of the current range[i, j]
- Creates a root node with value
nums[mid]
- Recursively builds the left subtree from range
[i, mid-1]
- Recursively builds the right subtree from range
[mid+1, j]
- Returns the constructed subtree rooted at
mid
The bit shift operation (i + j) >> 1
is used to calculate the middle index, which is equivalent to (i + j) // 2
but more efficient.
Intuition
When building a height-balanced BST from a sorted sequence, the key insight is that we need to choose the middle element as the root to ensure balance. This is because if we pick the middle element, we'll have roughly equal numbers of elements on both sides, which can form balanced left and right subtrees.
Think about it this way: if we have a sorted array [1, 2, 3, 4, 5]
, choosing 3
as the root gives us [1, 2]
for the left subtree and [4, 5]
for the right subtree. This naturally creates a balanced structure.
The challenge with a linked list is that we can't directly access the middle element in O(1) time like we can with an array. We could use the two-pointer technique (slow and fast pointers) to find the middle of the linked list each time, but this would be inefficient since we'd need to traverse the list multiple times during the recursive construction.
This leads us to a practical trade-off: convert the linked list to an array first. Yes, this requires O(n) extra space, but it allows us to access any element by index in O(1) time. Once we have the array, we can efficiently apply the divide-and-conquer approach:
- Find the middle index:
mid = (i + j) // 2
- Use
nums[mid]
as the root value - Recursively build the left subtree from the left half
[i, mid-1]
- Recursively build the right subtree from the right half
[mid+1, j]
This recursive pattern naturally maintains the BST property (left < root < right) because our original list is sorted, and it maintains balance because we're always splitting the remaining elements roughly in half. The recursion handles all the subtree construction automatically, making the solution elegant and straightforward.
Solution Approach
The implementation uses a two-phase approach: first converting the linked list to an array, then using DFS to construct the balanced BST.
Phase 1: Convert Linked List to Array
nums = []
while head:
nums.append(head.val)
head = head.next
We traverse the linked list once, storing all values in an array nums
. This takes O(n) time and O(n) space, but gives us random access to elements which is crucial for the next phase.
Phase 2: Construct BST using DFS
The core of the solution is the recursive dfs
function:
def dfs(i: int, j: int) -> Optional[TreeNode]:
if i > j:
return None
mid = (i + j) >> 1
l, r = dfs(i, mid - 1), dfs(mid + 1, j)
return TreeNode(nums[mid], l, r)
Let's break down how this works:
-
Base Case: When
i > j
, we've exhausted the current range, so we returnNone
to indicate no node exists here. -
Find Middle: We calculate
mid = (i + j) >> 1
. The bit shift operation>> 1
is equivalent to integer division by 2, giving us the middle index of the current range. -
Recursive Construction:
- First, we recursively build the left subtree from range
[i, mid - 1]
- Then, we recursively build the right subtree from range
[mid + 1, j]
- Notice the order here - we build both subtrees before creating the current node
- First, we recursively build the left subtree from range
-
Create Current Node: We create a new
TreeNode
with valuenums[mid]
, attaching the previously constructed left and right subtrees.
Example Walkthrough
For array [1, 2, 3, 4, 5]
(indices 0-4):
- Initial call:
dfs(0, 4)
, mid = 2, creates node with value 3- Left recursive call:
dfs(0, 1)
, mid = 0, creates node with value 1- Left:
dfs(0, -1)
returnsNone
- Right:
dfs(1, 1)
, mid = 1, creates node with value 2
- Left:
- Right recursive call:
dfs(3, 4)
, mid = 3, creates node with value 4- Left:
dfs(3, 2)
returnsNone
- Right:
dfs(4, 4)
, mid = 4, creates node with value 5
- Left:
- Left recursive call:
The final call dfs(0, len(nums) - 1)
returns the root of the complete balanced BST.
This divide-and-conquer pattern ensures that at each level, we're splitting the remaining elements roughly in half, maintaining the height-balanced property throughout the tree construction.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through converting the sorted linked list 1 -> 2 -> 3
to a height-balanced BST.
Step 1: Convert Linked List to Array
- Traverse the linked list:
1 -> 2 -> 3
- Create array:
nums = [1, 2, 3]
(indices 0, 1, 2)
Step 2: Build BST using DFS
We'll trace through the recursive calls:
Initial Call: dfs(0, 2)
- Calculate mid:
(0 + 2) >> 1 = 1
- This will create root node with value
nums[1] = 2
- But first, we need to build left and right subtrees
Left Subtree: dfs(0, 0)
- Calculate mid:
(0 + 0) >> 1 = 0
- Create node with value
nums[0] = 1
- Check left child:
dfs(0, -1)
→ returnsNone
(base case: 0 > -1) - Check right child:
dfs(1, 0)
→ returnsNone
(base case: 1 > 0) - Return node
1
with no children
Right Subtree: dfs(2, 2)
- Calculate mid:
(2 + 2) >> 1 = 2
- Create node with value
nums[2] = 3
- Check left child:
dfs(2, 1)
→ returnsNone
(base case: 2 > 1) - Check right child:
dfs(3, 2)
→ returnsNone
(base case: 3 > 2) - Return node
3
with no children
Final Assembly:
- Create root node with value
2
- Attach left child: node
1
- Attach right child: node
3
Result:
2 / \ 1 3
The tree is perfectly balanced with height difference of 0 between left and right subtrees, and maintains the BST property where left (1) < root (2) < right (3).
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7# Definition for a binary tree node.
8# class TreeNode:
9# def __init__(self, val=0, left=None, right=None):
10# self.val = val
11# self.left = left
12# self.right = right
13
14from typing import Optional
15
16class Solution:
17 def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
18 """
19 Convert a sorted linked list to a height-balanced binary search tree.
20
21 Args:
22 head: Head of the sorted linked list
23
24 Returns:
25 Root of the height-balanced BST
26 """
27
28 def build_bst(left: int, right: int) -> Optional[TreeNode]:
29 """
30 Recursively build a balanced BST from the values array.
31
32 Args:
33 left: Left boundary index (inclusive)
34 right: Right boundary index (inclusive)
35
36 Returns:
37 Root node of the subtree
38 """
39 # Base case: invalid range
40 if left > right:
41 return None
42
43 # Find the middle element to use as root
44 mid = (left + right) // 2
45
46 # Recursively build left and right subtrees
47 left_subtree = build_bst(left, mid - 1)
48 right_subtree = build_bst(mid + 1, right)
49
50 # Create root node with the middle value and attach subtrees
51 root = TreeNode(values[mid], left_subtree, right_subtree)
52
53 return root
54
55 # Convert linked list to array for O(1) access
56 values = []
57 current = head
58 while current:
59 values.append(current.val)
60 current = current.next
61
62 # Build BST from the sorted array
63 return build_bst(0, len(values) - 1)
64
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11/**
12 * Definition for a binary tree node.
13 * public class TreeNode {
14 * int val;
15 * TreeNode left;
16 * TreeNode right;
17 * TreeNode() {}
18 * TreeNode(int val) { this.val = val; }
19 * TreeNode(int val, TreeNode left, TreeNode right) {
20 * this.val = val;
21 * this.left = left;
22 * this.right = right;
23 * }
24 * }
25 */
26class Solution {
27 // List to store values from the linked list
28 private List<Integer> values = new ArrayList<>();
29
30 /**
31 * Converts a sorted linked list to a height-balanced BST.
32 *
33 * @param head The head of the sorted linked list
34 * @return The root of the height-balanced BST
35 */
36 public TreeNode sortedListToBST(ListNode head) {
37 // Convert linked list to array list for O(1) access
38 ListNode current = head;
39 while (current != null) {
40 values.add(current.val);
41 current = current.next;
42 }
43
44 // Build BST from the array list using divide and conquer
45 return buildBST(0, values.size() - 1);
46 }
47
48 /**
49 * Recursively builds a height-balanced BST from the sorted array.
50 * Uses divide and conquer approach by selecting middle element as root.
51 *
52 * @param left The left boundary index (inclusive)
53 * @param right The right boundary index (inclusive)
54 * @return The root node of the subtree
55 */
56 private TreeNode buildBST(int left, int right) {
57 // Base case: invalid range
58 if (left > right) {
59 return null;
60 }
61
62 // Find the middle index to ensure balanced tree
63 int mid = left + (right - left) / 2;
64
65 // Recursively build left subtree from elements before mid
66 TreeNode leftSubtree = buildBST(left, mid - 1);
67
68 // Recursively build right subtree from elements after mid
69 TreeNode rightSubtree = buildBST(mid + 1, right);
70
71 // Create root node with middle element and attach subtrees
72 TreeNode root = new TreeNode(values.get(mid), leftSubtree, rightSubtree);
73
74 return root;
75 }
76}
77
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11/**
12 * Definition for a binary tree node.
13 * struct TreeNode {
14 * int val;
15 * TreeNode *left;
16 * TreeNode *right;
17 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
18 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
19 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
20 * };
21 */
22class Solution {
23public:
24 /**
25 * Converts a sorted linked list to a height-balanced BST
26 * @param head - pointer to the head of the sorted linked list
27 * @return pointer to the root of the balanced BST
28 */
29 TreeNode* sortedListToBST(ListNode* head) {
30 // Convert linked list to vector for O(1) random access
31 vector<int> values;
32 ListNode* current = head;
33 while (current != nullptr) {
34 values.push_back(current->val);
35 current = current->next;
36 }
37
38 // Build BST from sorted array using divide and conquer
39 return buildBST(values, 0, values.size() - 1);
40 }
41
42private:
43 /**
44 * Recursively builds a balanced BST from a sorted array
45 * @param values - reference to the sorted array of values
46 * @param left - left boundary index (inclusive)
47 * @param right - right boundary index (inclusive)
48 * @return pointer to the root node of the subtree
49 */
50 TreeNode* buildBST(const vector<int>& values, int left, int right) {
51 // Base case: invalid range
52 if (left > right) {
53 return nullptr;
54 }
55
56 // Find middle element to ensure balanced tree
57 int mid = left + (right - left) / 2;
58
59 // Recursively build left and right subtrees
60 TreeNode* leftSubtree = buildBST(values, left, mid - 1);
61 TreeNode* rightSubtree = buildBST(values, mid + 1, right);
62
63 // Create root node with middle value and attach subtrees
64 TreeNode* root = new TreeNode(values[mid], leftSubtree, rightSubtree);
65
66 return root;
67 }
68};
69
1/**
2 * Definition for singly-linked list node
3 */
4interface ListNode {
5 val: number;
6 next: ListNode | null;
7}
8
9/**
10 * Definition for binary tree node
11 */
12interface TreeNode {
13 val: number;
14 left: TreeNode | null;
15 right: TreeNode | null;
16}
17
18/**
19 * Converts a sorted linked list to a height-balanced binary search tree
20 * @param head - The head of the sorted linked list
21 * @returns The root of the balanced BST
22 */
23function sortedListToBST(head: ListNode | null): TreeNode | null {
24 // Convert linked list to array for easier access by index
25 const sortedValues: number[] = [];
26 let currentNode: ListNode | null = head;
27
28 while (currentNode !== null) {
29 sortedValues.push(currentNode.val);
30 currentNode = currentNode.next;
31 }
32
33 /**
34 * Recursively builds a balanced BST from sorted array
35 * @param startIndex - The starting index of the current subarray
36 * @param endIndex - The ending index of the current subarray
37 * @returns The root node of the subtree
38 */
39 const buildBST = (startIndex: number, endIndex: number): TreeNode | null => {
40 // Base case: invalid range
41 if (startIndex > endIndex) {
42 return null;
43 }
44
45 // Calculate middle index using bit shift for integer division
46 const middleIndex: number = (startIndex + endIndex) >> 1;
47
48 // Recursively build left and right subtrees
49 const leftSubtree: TreeNode | null = buildBST(startIndex, middleIndex - 1);
50 const rightSubtree: TreeNode | null = buildBST(middleIndex + 1, endIndex);
51
52 // Create root node with middle value and attach subtrees
53 const rootNode: TreeNode = {
54 val: sortedValues[middleIndex],
55 left: leftSubtree,
56 right: rightSubtree
57 };
58
59 return rootNode;
60 };
61
62 // Start building BST from the entire array range
63 return buildBST(0, sortedValues.length - 1);
64}
65
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main phases:
- Converting the linked list to an array: This requires traversing the entire linked list once, taking
O(n)
time wheren
is the number of nodes in the linked list. - Building the BST recursively: The
dfs
function visits each element in the array exactly once to create a corresponding TreeNode. Although the recursion follows a divide-and-conquer pattern similar to merge sort, each element is processed only once, resulting inO(n)
time.
Therefore, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The space complexity consists of:
- The
nums
array that stores all values from the linked list:O(n)
space. - The output BST which contains
n
TreeNode objects:O(n)
space. - The recursion call stack: In the worst case, the recursion depth is
O(log n)
for a balanced tree construction.
Since O(n) + O(n) + O(log n) = O(n)
, the total space complexity is O(n)
.
Common Pitfalls
1. Inefficient Middle Element Access in Linked List
The Pitfall: A common mistake is trying to find the middle element directly in the linked list without converting it to an array first. This leads to repeatedly traversing the linked list to find middle elements during recursion.
# Inefficient approach - DON'T DO THIS
def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
def findMiddle(start, end):
slow = fast = start
prev = None
while fast != end and fast.next != end:
prev = slow
slow = slow.next
fast = fast.next.next
return slow, prev
def buildBST(start, end):
if start == end:
return None
mid, prev = findMiddle(start, end)
root = TreeNode(mid.val)
root.left = buildBST(start, mid)
root.right = buildBST(mid.next, end)
return root
return buildBST(head, None)
Why it's problematic: This approach has O(n log n) time complexity because finding the middle element takes O(n) time, and we do this for every recursive call. The total number of recursive calls is O(n), leading to overall O(n log n) complexity.
Solution: Convert the linked list to an array first (as shown in the correct implementation). This gives O(1) access to any element by index, reducing the overall time complexity to O(n).
2. Integer Overflow in Middle Calculation
The Pitfall: When calculating the middle index, using mid = (left + right) // 2
can cause integer overflow in languages with fixed integer sizes (though Python handles big integers automatically).
# Potential overflow in other languages mid = (left + right) // 2 # Could overflow if left + right > MAX_INT
Solution: Use either of these alternatives:
# Method 1: Bit shift (shown in original solution) mid = (left + right) >> 1 # Method 2: Avoid overflow explicitly mid = left + (right - left) // 2
3. Modifying the Original Linked List
The Pitfall: Some implementations might try to modify the original linked list structure during traversal, breaking the list for future use.
# BAD: Modifying the original list
def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
values = []
while head:
values.append(head.val)
temp = head
head = head.next
temp.next = None # DON'T DO THIS - destroys the original list
# ... rest of the code
Solution: Simply traverse the list without modifying it:
# GOOD: Preserve the original list
current = head
while current:
values.append(current.val)
current = current.next
4. Off-by-One Errors in Recursion Boundaries
The Pitfall: Incorrectly handling the recursion boundaries, especially mixing inclusive and exclusive ranges.
# WRONG: Inconsistent boundary handling
def build_bst(left: int, right: int) -> Optional[TreeNode]:
if left >= right: # Wrong condition for inclusive boundaries
return None
mid = (left + right) // 2
left_subtree = build_bst(left, mid) # Should be mid - 1
right_subtree = build_bst(mid + 1, right)
# ...
Solution: Be consistent with inclusive boundaries:
# CORRECT: Consistent inclusive boundaries
def build_bst(left: int, right: int) -> Optional[TreeNode]:
if left > right: # Correct condition for inclusive boundaries
return None
mid = (left + right) // 2
left_subtree = build_bst(left, mid - 1) # Exclusive of mid
right_subtree = build_bst(mid + 1, right) # Exclusive of mid
# ...
5. Not Handling Edge Cases
The Pitfall: Forgetting to handle empty lists or single-node lists properly.
Solution: The base case if left > right: return None
naturally handles these edge cases:
- Empty list:
build_bst(0, -1)
returnsNone
- Single node:
build_bst(0, 0)
creates one node with both children asNone
Which data structure is used to implement priority queue?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Want a Structured Path to Master System Design Too? Don’t Miss This!