Stream a BST's values in sorted order, one next() at a time, by pausing an iterative in-order traversal on an explicit stack. The constructor stacks the leftmost spine; each next() pops the smallest unvisited node and stacks the left spine of its right child — amortized O(1) per call.
Build an iterator over a BST that yields values in sorted (in-order) order, exposing next() (return the next-smallest value) and hasNext() (is there another value?). Both must be average O(1) time and O(h) space — you cannot pre-flatten the whole tree into a sorted array (that would be O(n) space).
Worked example — tree [7,3,15,null,null,9,20] (root 7, left 3, right 15 whose children are 9 and 20):
next() → 3next() → 7hasNext() → truenext() → 9hasNext() → truenext() → 15hasNext() → truenext() → 20hasNext() → falsenext() pops the top (the smallest unvisited node), then stacks the left spine of its right child so the new smallest sits on top again — leaving the invariant intact for the following call.pushLeft(node) pushes node and every left descendant, so the smallest of that subtree lands on top. This is the one move both the constructor and next() reuse.pushLeft(root). The stack now holds the path from the root down to the minimum node, with the minimum on top.pushLeft(node.right): the popped node's left subtree is finished, so the only smaller-than-everything-else candidates now live in its right subtree's left spine.stack.length > 0. A non-empty stack means there is still a node waiting to be popped.next() can push a whole spine. But each node is pushed once and popped once across the entireiteration, so the total work over n calls is O(n) — that's amortized O(1) per next(), not O(h). The stack itself never exceeds the tree's height h, giving O(h) space.A single next() may push up to h nodes (a long left spine) or zero. But summed over a full traversal, every node is pushed and popped exactly once, so n calls cost O(n) total — O(1) each on average. Space is O(h): the stack only ever holds nodes along one root-to-node path — O(log n) balanced, O(n) degenerate.
k calls to next(), and the same "freeze the traversal in a member structure" trick powers Flatten Nested List Iterator and Peeking Iterator.next().1class BSTIterator {2 private stack: TreeNode[] = [];34▶ constructor(root: TreeNode | null) {5▶ this.pushLeft(root); // seed: leftmost spine6▶ }78 private pushLeft(node: TreeNode | null): void {9 while (node !== null) {10 this.stack.push(node); // remember ancestors11 node = node.left; // dive to the smallest12 }13 }1415 next(): number {16 const node = this.stack.pop()!; // smallest unvisited17 this.pushLeft(node.right); // open up its right spine18 return node.val;19 }2021 hasNext(): boolean {22 return this.stack.length > 0; // anything left to pop?23 }24}
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
class BSTIterator {
private stack: TreeNode[] = [];
constructor(root: TreeNode | null) {
this.pushLeft(root); // seed with the leftmost spine
}
// Push a node and every left descendant; the smallest ends up on top.
private pushLeft(node: TreeNode | null): void {
while (node !== null) {
this.stack.push(node);
node = node.left;
}
}
next(): number {
const node = this.stack.pop()!; // next-smallest unvisited node
this.pushLeft(node.right); // its left subtree is done; open the right
return node.val;
}
hasNext(): boolean {
return this.stack.length > 0; // unvisited nodes remain iff stack non-empty
}
}stackpersists between calls and holds the ancestors-still-owed-a-visit, with the next value to return on top. This single field is the whole "paused traversal" state.next() is ready.node's subtree ends up on top. Called with rootat construction and with each popped node's right child during next().stack.pop() is the next-smallest node. Its left subtree is already exhausted, so call pushLeft(node.right) to stack the left spine of its right subtree (the new candidates), then return node.val.stack.length > 0 answers it in O(1).hasNext() is O(1). next() is amortized O(1): a single call can push up to h nodes, but each node is pushed and popped once over the full traversal, so n calls total O(n). Space is O(h) — the stack holds at most one root-to-node path (O(log n) balanced, O(n) degenerate).next() calls is O(n) → O(1) each on average.next() calls.next() from whichever has the smaller current head — like merging two sorted lists.class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
class BSTIterator {
private stack: TreeNode[] = [];
constructor(root: TreeNode | null) {
this.pushLeft(root); // seed with the leftmost spine
}
// Push a node and every left descendant; the smallest ends up on top.
private pushLeft(node: TreeNode | null): void {
while (node !== null) {
this.stack.push(node);
node = node.left;
}
}
next(): number {
const node = this.stack.pop()!; // next-smallest unvisited node
this.pushLeft(node.right); // its left subtree is done; open the right
return node.val;
}
hasNext(): boolean {
return this.stack.length > 0; // unvisited nodes remain iff stack non-empty
}
}hasNext() is O(1). next() is amortized O(1): a single call can push up to h nodes, but each node is pushed and popped once over the full traversal, so n calls total O(n). Space is O(h) — the stack holds at most one root-to-node path (O(log n) balanced, O(n) degenerate).Do the whole in-order traversal up front into a sorted array and keep an index pointer. Dead simple, and each next() is a true O(1) array read — but it stores all n values regardless of how few the caller consumes.
class BSTIterator {
private values: number[] = [];
private i = 0;
constructor(root: TreeNode | null) {
const inorder = (node: TreeNode | null): void => {
if (node === null) return;
inorder(node.left);
this.values.push(node.val);
inorder(node.right);
};
inorder(root);
}
next(): number {
return this.values[this.i++];
}
hasNext(): boolean {
return this.i < this.values.length;
}
}O(n) space and pays the full traversal cost in the constructor even if the caller only ever calls next() a few times. The stack version trims this to O(h) space with amortized O(1) calls.| "iterator over a BST in sorted order" | paused iterative in-order + stack |
| next() / hasNext() with O(h) memory | member stack of waiting ancestors |
| pop the smallest, then go right | pushLeft(node.right) after each pop |
| "stream sorted values one at a time" | freeze the traversal between calls |
class BSTIterator {
private stack: TreeNode[] = [];
constructor(root: TreeNode | null) { this.pushLeft(root); }
private pushLeft(node: TreeNode | null): void {
while (node) { this.stack.push(node); node = node.left; }
}
next(): number {
const node = this.stack.pop()!;
this.pushLeft(node.right);
return node.val;
}
hasNext(): boolean { return this.stack.length > 0; }
}stack holding the path of waiting ancestors, with the next-smallest node on top — the frozen state of an iterative in-order traversal.