Pop stack with invalid left subtree indices (2,1) and skip
Pop (2,1,node3,True). Since left > right, skip processing this invalid subarray.
💡 Subarrays with left > right represent empty subtrees and are ignored.
Line:if left > right:
continue
💡 Algorithm correctly handles empty subtrees by ignoring invalid ranges.
reconstruct
Balanced BST construction complete, return root node 2
Stack is empty, construction finished. Return root node 2 representing balanced BST.
💡 Final balanced BST root is ready for use.
Line:return root
💡 Balanced BST root is node 2 with left child 1 and right subtree rooted at 3 with right child 4.
from typing import Optional, List
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def balanceBST(root: Optional[TreeNode]) -> Optional[TreeNode]:
nodes = [] # STEP 1: Initialize list to collect nodes
def inorder(node: Optional[TreeNode]):
if not node: # STEP 2: Base case for recursion
return
inorder(node.left) # STEP 4,5,8,11: Traverse left subtree
nodes.append(node) # STEP 3,6,9,12: Add node to list
inorder(node.right) # STEP 4,7,10: Traverse right subtree
inorder(root) # STEP 1-12: Collect nodes in sorted order
if not nodes:
return None
stack = [(0, len(nodes) - 1, None, True)] # STEP 13: Initialize stack
root = None
while stack:
left, right, parent, is_left = stack.pop() # STEP 14,16,18,19: Pop stack
if left > right: # STEP 19: Skip invalid ranges
continue
mid = (left + right) // 2
node = nodes[mid]
node.left = node.right = None
if parent:
if is_left:
parent.left = node
else:
parent.right = node
else:
root = node
stack.append((mid + 1, right, node, False)) # STEP 15,17: Push right subtree
stack.append((left, mid - 1, node, True)) # STEP 15,17: Push left subtree
return root # STEP 20: Return balanced BST root
📊
Balance a BST - Watch the Algorithm Execute, Step by Step
Watching the algorithm step-by-step reveals how the skewed BST is transformed into a balanced one by reorganizing nodes based on inorder traversal.
Step 1/20
·Active fill★Answer cell
Current node: 1
1234
DFS Stack
1 (entered)
1234
DFS Stack
1 (left_done)
Current node: 1
1234
DFS Stack
1 (node_added)
Result: [1]
Current node: 2
1234
DFS Stack
2 (entered)
Result: [1]
1234
DFS Stack
2 (left_done)
Result: [1]
Current node: 2
1234
DFS Stack
2 (node_added)
Result: [1, 2]
Current node: 3
1234
DFS Stack
3 (entered)
Result: [1, 2]
1234
DFS Stack
3 (left_done)
Result: [1, 2]
Current node: 3
1234
DFS Stack
3 (node_added)
Result: [1, 2, 3]
Current node: 4
1234
DFS Stack
4 (entered)
Result: [1, 2, 3]
1234
DFS Stack
4 (left_done)
Result: [1, 2, 3]
Current node: 4
1234
Result: [1, 2, 3, 4]
1234
Result: [1, 2, 3, 4]
Current node: 2
1234
Result: [1, 2, 3, 4]
Current node: 2
1234
Result: [1, 2, 3, 4]
Current node: 3
1234
Result: [1, 2, 3, 4]
Current node: 3
1234
Result: [1, 2, 3, 4]
Current node: 4
1234
Result: [1, 2, 3, 4]
1234
Result: [1, 2, 3, 4]
Current node: 2
1234
Result: [1, 2, 3, 4]
Return: 2
Key Takeaways
✓ Inorder traversal collects nodes in sorted order, which is the foundation for balancing the BST.
This insight is hard to see from code alone because the traversal and balancing are separate phases.
✓ Choosing the middle node of the sorted nodes array as root at each step ensures the tree is balanced.
Visualizing the middle selection clarifies how the tree height is minimized.
✓ Using a stack to iteratively build the balanced BST replaces recursion and manages subtree construction explicitly.
Seeing the stack operations helps understand how the algorithm controls subtree boundaries.
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. Consider the following code snippet implementing the optimal approach for Two Sum IV in BST. Given the BST with nodes [5,3,6,2,4,null,7] and target k=9, what is the value of variable s during the first iteration of the while loop?
Hint: Inorder array first + last sum equals target [OK]
Common Mistakes:
Off-by-one error in indexing left or right
Confusing node values with indices
Miscomputing sum s
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. Suppose you want to extend the BST Iterator to support repeated calls to next() that return the same element multiple times (i.e., allow reuse of elements). Which modification to the Morris traversal based iterator is correct?
hard
A. Remove thread restoration to keep the tree threaded and allow repeated visits to the same node.
B. Store all elements in a list during initialization to allow repeated next() calls without traversal.
C. Modify _advance() to not move current pointer after returning next_val, so next() returns same value again.
D. Use a stack-based iterator and push nodes multiple times to simulate repeated next() calls.
Solution
Step 1: Understand reuse requirement
Allowing repeated next() calls to return the same element requires storing elements since Morris traversal advances and loses previous state.
Step 2: Evaluate modifications
Removing thread restoration (A) corrupts tree, modifying _advance() to not move current (C) breaks traversal logic, and pushing nodes multiple times on stack (B) is inefficient and incorrect. Storing all elements upfront (D) enables repeated next() calls reliably.
Final Answer:
Option B -> Option B
Quick Check:
Pre-storing elements supports repeated next() calls without traversal [OK]
Hint: Reuse requires storing elements, not traversal tricks [OK]
Common Mistakes:
Assuming traversal can be paused and repeated
Ignoring tree corruption from missing restoration
Trying to simulate reuse with threads
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