class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def __init__(self):
self.index = 0
def helper(self, nums, left, right):
# STEP 1-20: Recursive helper to build BST
if left > right:
return None # STEP 5,9,15,19 base case
mid = (left + right) // 2 # STEP 2,4,8,14,18 mid calculation
left_child = self.helper(nums, left, mid - 1) # STEP 3,5,7,9,15,19 recurse left
root = TreeNode(nums[mid]) # STEP 6,10,12,16,20 create node
self.index += 1 # STEP 6,10,12,16,20 increment index
root.left = left_child # STEP 12 attach left
root.right = self.helper(nums, mid + 1, right) # STEP 7,13,17 recurse right
return root # STEP 11,20 return node
def sorted_array_to_bst(self, nums):
self.index = 0 # STEP 1 initialize
return self.helper(nums, 0, len(nums) - 1) # STEP 1 start recursion
if __name__ == '__main__':
nums = [-10, -3, 0, 5, 9]
sol = Solution()
root = sol.sorted_array_to_bst(nums)
def inorder(node):
return inorder(node.left) + [node.val] + inorder(node.right) if node else []
print(inorder(root))
📊
Convert Sorted Array to Height-Balanced BST - Watch the Algorithm Execute, Step by Step
Watching each recursive call and node creation helps you understand how the algorithm balances the tree by choosing midpoints and how recursion unwinds to assemble the final BST.
✓ Choosing the middle element as root at each recursive step ensures the BST is height-balanced.
This insight is hard to see from code alone but clear when watching the recursive splits and node creations.
✓ The recursion builds left subtree first, then root, then right subtree, which is crucial for correct BST construction.
Understanding the order of recursion helps grasp how the tree nodes are linked properly.
✓ Base cases where left > right stop recursion and return None, marking leaf nodes' children.
Visualizing base cases clarifies when recursion ends and how leaf nodes are formed.
Practice
(1/5)
1. You are given a binary search tree and two nodes within it. You need to find the node that is the deepest ancestor common to both nodes, leveraging the BST property to optimize the search. Which approach guarantees the most efficient solution?
easy
A. Perform two separate DFS traversals to find paths from root to each node, then compare paths to find the last common node.
B. Apply dynamic programming to store ancestors of each node and then find the lowest common ancestor by comparing stored data.
C. Iteratively traverse the BST from the root, moving left or right depending on the values of the two nodes relative to the current node, stopping when the current node splits the paths to the two nodes.
D. Use a greedy approach that always moves towards the node with the smaller value until both nodes are found.
Solution
Step 1: Understand BST property usage
In a BST, left subtree nodes are smaller and right subtree nodes are larger than the current node. This allows pruning the search space efficiently.
Step 2: Identify optimal traversal
Starting from root, if both target nodes are smaller, go left; if both are larger, go right; else current node is the split point and thus the LCA.
Final Answer:
Option C -> Option C
Quick Check:
Iterative BST traversal uses O(h) time and O(1) space [OK]
Hint: Use BST property to prune search paths [OK]
Common Mistakes:
Using path comparison wastes time and space
Greedy approach ignores split point logic
Dynamic programming is unnecessary overhead
2. What is the time complexity of the iterative inorder traversal approach to find the kth smallest element in a BST, where H is the height of the tree and k is the input parameter?
medium
A. O(H + k), where H is the height of the tree and k is the kth element to find
B. O(k), since only k nodes are visited
C. O(k log n), assuming balanced BST
D. O(n), where n is the total number of nodes in the BST
Solution
Step 1: Analyze traversal steps
The algorithm traverses down to the leftmost node (cost O(H)) and then visits k nodes in inorder sequence.
Step 2: Combine costs
Total time is O(H) to reach leftmost node plus O(k) to visit k nodes, so O(H + k).
Final Answer:
Option A -> Option A
Quick Check:
Traversal cost depends on height plus k nodes visited [OK]
Hint: Traversal cost includes height plus k nodes visited [OK]
Common Mistakes:
Assuming O(n) always
Ignoring height cost
Assuming only k nodes visited without stack overhead
3. What is the worst-case time complexity of the optimal iterative BST search algorithm when searching for a value in a BST with n nodes and height h?
medium
A. O(log n) always because BSTs are balanced.
B. O(n) because you might have to visit every node in the tree.
C. O(h) because the search path follows the height of the tree.
D. O(1) because the search uses direct indexing.
Solution
Step 1: Understand BST height and search path
The search path length is at most the height h of the BST, as each step moves down one level.
Step 2: Analyze worst-case time complexity
In worst case (unbalanced tree), h can be up to n, but complexity is expressed as O(h), not O(n) directly.
Final Answer:
Option C -> Option C
Quick Check:
Search complexity depends on tree height, not total nodes necessarily [OK]
Hint: Time depends on tree height, not total nodes always [OK]
Common Mistakes:
Assuming O(log n) always, ignoring unbalanced trees
4. The following code attempts to find if there exist two distinct nodes in a BST whose values sum to k. Identify the line containing the subtle bug that can cause incorrect results.
medium
A. Line 6: dfs(node.left)
B. Line 4: if k - node.val in seen:
C. Line 7: seen.add(node.val)
D. Line 8: dfs(node.right)
Solution
Step 1: Understand DFS and set usage
The code checks if complement exists in seen before adding current node's value.
Step 2: Identify bug in order of operations
Checking complement before adding current node's value misses pairs where both nodes are the same, or misses valid pairs if current node's value is needed for complement checks in children.
Final Answer:
Option B -> Option B
Quick Check:
Complement check must happen after adding current node's value to seen [OK]
Hint: Add current node to set before checking complements [OK]
Common Mistakes:
Checking complement before adding current node's value
Not handling None nodes properly
Returning False too early
5. Suppose you want to modify the BST insertion algorithm to allow inserting duplicate values by always inserting duplicates into the left subtree. Which of the following changes to the iterative insertion code below correctly implements this behavior without breaking the BST property?
def insertIntoBST(root, val):
if root is None:
return TreeNode(val)
curr = root
while curr:
parent = curr
if val < curr.val:
if curr.left is None:
curr.left = TreeNode(val)
return root
curr = curr.left
else:
if curr.right is None:
curr.right = TreeNode(val)
return root
curr = curr.right
return root
hard
A. Change the condition to if val <= curr.val: and insert duplicates to the left subtree.
B. Change the condition to if val < curr.val: and insert duplicates to the right subtree.
C. Always insert duplicates as new root nodes to maintain BST property.
D. Disallow duplicates by returning the original tree without insertion.
Solution
Step 1: Understand duplicate insertion rule
Duplicates must go to the left subtree, so values equal to current node should be treated as less or equal.
Step 2: Modify comparison operator
Changing condition to val <= curr.val ensures duplicates follow left subtree insertion path, preserving BST property.
Final Answer:
Option A -> Option A
Quick Check:
Duplicates inserted left by using <= comparison [OK]
Hint: Use <= to direct duplicates left, preserving BST order [OK]
Common Mistakes:
Inserting duplicates right breaks problem requirement