Implement the BSTIterator class that represents an iterator over the in-order traversal of a binary search tree (BST). Implement the following functions:
BSTIterator(TreeNode root) Initializes an object of the BSTIterator class. The root of the BST is given as part of the constructor. The pointer should be initialized to a non-existent number smaller than any element in the BST. Therefore, hasPrev() should return false and prev() should return an error.boolean hasNext() Returns true if there exists a number in the traversal to the right of the pointer, or false otherwise.int next() Moves the pointer to the right, returns the number at the pointer, and returns the number at the pointer.boolean hasPrev() Returns true if there exists a number in the traversal to the left of the pointer, or false otherwise.int prev() Moves the pointer to the left, returns the number at the pointer, and returns the number at the pointer.Notice that by initializing the pointer to a non-existent smallest number, the first call to next() will return the smallest element in the BST.
You may assume that next() and prev() calls will always be valid. That is, there will be at least a next/previous number in the in-order traversal when next()/prev() is called.
Example:
Input ["BSTIterator", "next", "next", "prev", "next", "hasPrev", "prev", "next", "hasNext"] [[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], []] Output [null, 3, 7, 3, 7, true, 3, 9, true] Explanation BSTIterator bSTIterator = new BSTIterator([7, 3, 15, null, null, 9, 20]); bSTIterator.next(); // return 3 bSTIterator.next(); // return 7 bSTIterator.prev(); // return 3 bSTIterator.next(); // return 7 bSTIterator.hasPrev(); // return True bSTIterator.prev(); // return 3 bSTIterator.next(); // return 9 bSTIterator.hasNext(); // return True
Constraints:
[1, 105].0 <= Node.val <= 106105 calls will be made to hasNext, next, hasPrev, and prev.Follow up: Could you solve the problem without precalculating the in-order traversal?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach is about listing every single node in the tree in the order we need them and going through that list. We'll keep this list handy so we can just pick the next one when asked. This is like making a cheat sheet of all the nodes in the right order before the test even starts.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
class BinarySearchTreeIterator:
def __init__(self, root: TreeNode):
self.ordered_list = []
self.current_index = -1
self.inorder_traversal(root)
def inorder_traversal(self, root: TreeNode):
if root:
self.inorder_traversal(root.left)
self.ordered_list.append(root.value)
self.inorder_traversal(root.right)
def hasNext(self) -> bool:
return self.current_index < len(self.ordered_list) - 1
def next(self) -> int:
# Advance the index to the next element.
self.current_index += 1
return self.ordered_list[self.current_index]
def hasPrev(self) -> bool:
return self.current_index > 0
def prev(self) -> int:
# Move the index back to the previous element.
self.current_index -= 1
return self.ordered_list[self.current_index]The challenge is to navigate a binary search tree and remember the path we've taken so we can efficiently move both forward and backward. We maintain two stacks: one to keep track of the path from the root to the current node, and another to store nodes we've previously visited for backtracking.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
class BSTIterator:
def __init__(self, root: TreeNode):
self.forward_stack = []
self.backward_stack = []
self._push_all_left(root)
def _push_all_left(self, node: TreeNode):
# Push all left nodes starting from node
while node:
self.forward_stack.append(node)
node = node.left
def hasNext(self) -> bool:
return len(self.forward_stack) > 0
def next(self) -> int:
node = self.forward_stack.pop()
self.backward_stack.append(node)
# If it has a right, push all the way left
if node.right:
self._push_all_left(node.right)
return node.value
def hasPrev(self) -> bool:
return len(self.backward_stack) > 0
def prev(self) -> int:
node = self.backward_stack.pop()
# Push current node back to forward stack so next() can work
self.forward_stack.append(node)
return node.value| Case | How to Handle |
|---|---|
| Null root node | The constructor should handle a null root by initializing an empty state (e.g., empty stack). |
| Tree with only one node | hasNext() returns true, next() returns the node's value, and hasNext() then returns false. |
| Tree with all nodes having the same value | The in-order traversal logic should still function correctly, visiting all nodes sequentially. |
| Completely unbalanced tree (skewed left or right) | Ensure the stack storing previous nodes doesn't exceed memory limits for very deep trees. |
| Calling next() after hasNext() returns false | Throw an exception like NoSuchElementException to indicate invalid operation. |
| Very large tree (millions of nodes) | Ensure the in-order traversal algorithm scales efficiently and doesn't cause stack overflow in recursion, potentially using an iterative approach. |
| Calling previous() multiple times when at the smallest value | Ensure that repeated calls to previous() when at the smallest value correctly handles empty stack edge case or some other marker to prevent errors. |
| Integer overflow in node values | Use long to avoid integer overflow when comparing or calculating values if the data type allows for larger range. |