BST Iterator Design in DSA C - Time & Space Complexity
We want to understand how fast the BST Iterator works when we move through the tree.
How does the time to get the next smallest item change as the tree grows?
Analyze the time complexity of the following BST Iterator code snippet.
// BST Iterator structure
struct BSTIterator {
Stack* stack;
};
// Initialize iterator with root
void bstIteratorInit(BSTIterator* it, TreeNode* root) {
while (root != NULL) {
stackPush(it->stack, root);
root = root->left;
}
}
// Get next smallest element
int bstIteratorNext(BSTIterator* it) {
TreeNode* node = stackPop(it->stack);
int val = node->val;
node = node->right;
while (node != NULL) {
stackPush(it->stack, node);
node = node->left;
}
return val;
}
This code initializes the iterator by pushing all left children, then each next call pops the top node and pushes left children of its right subtree.
Look at what repeats when we call next.
- Primary operation: Pushing nodes onto the stack while going left.
- How many times: Each node is pushed and popped exactly once during the entire iteration.
Each node is handled once, but some next calls do more work pushing nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 pushes and 10 pops spread over calls |
| 100 | About 100 pushes and 100 pops total |
| 1000 | About 1000 pushes and 1000 pops total |
Pattern observation: Total work grows linearly with number of nodes, but each next call may vary in cost.
Time Complexity: O(1) amortized per next()
This means each next call takes constant time on average, even if some calls do more work.
[X] Wrong: "Each next() call always takes O(h) time where h is tree height."
[OK] Correct: Some calls do O(h) work, but over all nodes, the total work spreads out, making average next() call O(1).
Understanding amortized time helps you explain why this iterator is efficient and practical for large trees.
What if we changed the iterator to push all nodes at once during initialization? How would the time complexity of next() change?
Practice
BST Iterator in a binary search tree?Solution
Step 1: Understand BST Iterator functionality
A BST Iterator is designed to traverse the tree in sorted order, typically ascending, without using recursion.Step 2: Identify the main purpose
It provides a way to get the next smallest element efficiently, which matches To traverse the BST in ascending order without recursion.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
- Thinking it balances the tree automatically
- Assuming it uses recursion internally
Solution
Step 1: Identify language and syntax
The question is about C language, so C++ or other language syntax is invalid.Step 2: Choose correct C stack declaration
int stack[100]; int top = -1; declares a fixed-size integer array and a top index, which is the typical way to implement a stack in C.Final Answer:
int stack[100]; int top = -1; -> Option AQuick Check:
C stack = array + top index [OK]
- Using C++ or other language syntax in C
- Using dynamic lists not available in C
- Using var keyword which is not in C
next() calls?
typedef struct Node {
int val;
struct Node* left;
struct Node* right;
} Node;
// Assume stack and top are global
void push(Node* node) { stack[++top] = node; }
Node* pop() { return stack[top--]; }
void pushLeft(Node* root) {
while (root) {
push(root);
root = root->left;
}
}
Node* next() {
Node* node = pop();
pushLeft(node->right);
return node;
}
// BST structure:
// 5
// / \
// 3 7
// / \ \
// 2 4 8
// Initial call: pushLeft(root) where root = 5
// Calls: next(), next(), next(), next(), next(), next()Solution
Step 1: Understand pushLeft and next behavior
pushLeft pushes all left children starting from root. next pops top node, then pushes left children of right subtree.Step 2: Trace calls step-by-step
Initial stack after pushLeft(5): [5,3,2] (top is 2). next() pops 2 (no right child), output 2.
next() pops 3, pushes left of 4 (which is 4), output 3.
next() pops 4 (no right child), output 4.
next() pops 5, pushes left of 7 (which is 7), output 5.
next() pops 7, pushes left of 8 (which is 8), output 7.
next() pops 8, output 8.Final Answer:
[2, 3, 4, 5, 7, 8] -> Option CQuick Check:
In-order traversal = [2,3,4,5,7,8] [OK]
- Ignoring pushLeft on right child
- Assuming preorder or postorder output
- Mixing up stack order
typedef struct Node {
int val;
struct Node* left;
struct Node* right;
} Node;
Node* stack[100];
int top = -1;
void push(Node* node) { stack[++top] = node; }
Node* pop() { return stack[top--]; }
void pushLeft(Node* root) {
while (root != NULL) {
push(root);
root = root->left;
}
}
Node* next() {
Node* node = pop();
pushLeft(node->right);
return node;
}
int hasNext() {
return top >= 0;
}
// Usage:
// pushLeft(root);
// while (hasNext()) {
// Node* n = next();
// printf("%d ", n->val);
// }
Solution
Step 1: Analyze stack usage in pushLeft
pushLeft pushes all left nodes onto stack. For a very deep BST, this can exceed stack size 100 causing overflow.Step 2: Check other functions for errors
pop() assumes stack not empty; hasNext() correctly checks top >= 0; pushLeft handles NULL root by stopping loop.Final Answer:
Stack overflow if BST is large and pushLeft pushes too many nodes -> Option BQuick Check:
Fixed stack size risks overflow [OK]
- Assuming pop() returns NULL safely
- Thinking pushLeft fails on NULL root
- Believing hasNext() logic is incorrect
next() and hasNext() in O(1) average time and uses O(h) memory, where h is tree height. Which approach best achieves this?Solution
Step 1: Understand time and space constraints
O(1) average time per next() and O(h) memory means no full traversal storage and no recursion per call.Step 2: Evaluate options
Use a stack to store left children, pushing right subtree nodes on next() calls uses a stack to store left children only, pushing right subtree nodes lazily, matching constraints.
Store all nodes in an array by in-order traversal at initialization uses O(n) memory, violating O(h).
Use recursion to traverse nodes on each next() call uses recursion each call, not O(1) average.
Use a queue to store nodes level by level uses queue level order, not in-order traversal.Final Answer:
Use a stack to store left children, pushing right subtree nodes on next() calls -> Option AQuick Check:
Stack + lazy push = O(1) avg time & O(h) space [OK]
- Precomputing all nodes wastes memory
- Using recursion each next() call is slow
- Using queue gives wrong traversal order
Ready to prove it?
Pass the test and this pattern moves unknown
