BST Iterator Design in DSA C# - Time & Space Complexity
We want to understand how fast the BST Iterator works when we ask for the next smallest element.
How does the time needed change as the tree gets bigger?
Analyze the time complexity of the following BST Iterator code snippet.
public class BSTIterator {
private Stack stack = new Stack();
public BSTIterator(TreeNode root) {
PushLeft(root);
}
private void PushLeft(TreeNode node) {
while (node != null) {
stack.Push(node);
node = node.left;
}
}
public int Next() {
TreeNode node = stack.Pop();
PushLeft(node.right);
return node.val;
}
public bool HasNext() {
return stack.Count > 0;
}
}
This code creates an iterator that returns the next smallest value in a binary search tree each time Next() is called.
Look at what repeats when we call Next().
- Primary operation: Pushing nodes along the left branch of a subtree onto the stack.
- How many times: Each node is pushed and popped exactly once during the entire iteration.
Each node is visited once, but the work per Next() call varies.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 pushes and 10 pops total, spread over calls. |
| 100 | About 100 pushes and 100 pops total, spread over calls. |
| 1000 | About 1000 pushes and 1000 pops total, spread over calls. |
Pattern observation: Total work grows linearly with number of nodes, but each Next() call does a small part of this work.
Time Complexity: O(1) amortized per Next()
This means each call to Next() takes constant time on average, even though some calls do more work.
[X] Wrong: "Each Next() call always takes O(h) time because it pushes nodes down the left branch."
[OK] Correct: Actually, each node is pushed and popped only once total, so the cost spreads out over all Next() calls, making average time O(1).
Understanding this iterator's time complexity shows you can analyze amortized costs and explain how data structures work efficiently over many operations.
"What if we changed the BST Iterator to use recursion instead of a stack? How would the time complexity per Next() call change?"
Practice
Solution
Step 1: Understand BST Iterator functionality
A BST Iterator is designed to traverse the tree in sorted order, usually ascending, without using recursion explicitly.Step 2: Identify the main purpose
Among the options, only traversing in ascending order without recursion matches the BST Iterator's role.Final Answer:
To traverse the BST in ascending order without recursion -> Option DQuick Check:
BST Iterator = In-order traversal without recursion [OK]
- Confusing iterator with insertion or deletion operations
- Thinking it balances the tree automatically
- Assuming it uses recursion internally
Next() method in a BST Iterator class that returns the next smallest integer?Solution
Step 1: Determine the return type of Next()
The Next() method should return the next smallest integer value from the BST, so its return type must be int.Step 2: Check method signature correctness
Onlypublic int Next()matches the expected signature returning an integer value.Final Answer:
public int Next() -> Option CQuick Check:
Next() returns int value [OK]
- Using void return type for Next()
- Returning bool instead of int
- Returning string instead of int
Next() five times on a correctly implemented BST Iterator?Solution
Step 1: Construct BST and find in-order traversal
Inserting 7, 3, 15, 9, 20 creates BST:
7 is root
3 left of 7
15 right of 7
9 left of 15
20 right of 15
In-order traversal (left-root-right) is [3, 7, 9, 15, 20].Step 2: Understand Next() output sequence
Calling Next() five times returns nodes in in-order sequence: 3, 7, 9, 15, 20.Final Answer:
[3, 7, 9, 15, 20] -> Option BQuick Check:
In-order traversal = Next() outputs [3,7,9,15,20] [OK]
- Confusing insertion order with traversal order
- Reversing the output sequence
- Mixing left and right subtree nodes
Next() method snippet and choose the correct fix:public int Next() {
TreeNode node = stack.Pop();
if (node.right != null) {
stack.Push(node.right);
}
return node.val;
}Solution
Step 1: Analyze the Next() method logic
After popping node, if node.right exists, we must push node.right and all its left descendants to maintain in-order traversal.Step 2: Identify missing step
Current code pushes only node.right, missing its left subtree nodes, causing incorrect traversal order.Final Answer:
Push all left children of node.right onto stack, not just node.right -> Option AQuick Check:
Push left subtree nodes after right child [OK]
- Pushing only right child without left descendants
- Returning value before popping
- Checking left child instead of right child
HasNext() and Next() methods with O(1) average time per call and O(h) space, where h is tree height. Which approach best achieves this?Solution
Step 1: Understand time and space constraints
O(1) average time per call and O(h) space means we cannot store all nodes upfront or use recursion each call.Step 2: Evaluate approaches
Using a stack to store left children and pushing left descendants of right child on Next() achieves these constraints efficiently.Final Answer:
Use a stack to store left children nodes and push left descendants of right child on Next() -> Option AQuick Check:
Stack with left children = O(1) avg time, O(h) space [OK]
- Storing all nodes wastes O(n) space
- Using recursion causes higher time complexity
- Using queue breaks in-order traversal order
Ready to prove it?
Pass the test and this pattern moves unknown
