- Inorder Traversal Using Extra Space
- Controlled Morris Traversal
- Stack and Controlled Iterative Inorder Traversal - Optimized
The simplest way to implement a binary search tree iterator is to perform an inorder traversal of the tree and store the results in a list. When next() is called, we simply return the next element in the list. This approach requires more space to store the elements of the tree in a list.
- Perform an inorder traversal of the BST and store the values in a list.
- Use an index to iterate through this list for the
next()calls. - Use the list’s length for
hasNext()to check if there are any more elements left.
class BSTIterator:
def __init__(self, root):
# Initialize the list and perform inorder traversal
self.nodes_list = []
self.index = -1
self._inorder_traversal(root)
def _inorder_traversal(self, node):
# Helper function to perform inorder traversal and fill the list
if node is None:
return
self._inorder_traversal(node.left)
self.nodes_list.append(node.val)
self._inorder_traversal(node.right)
def next(self):
# Move the index and return the next element
self.index += 1
return self.nodes_list[self.index]
def hasNext(self):
# Check if there are more elements in the list
return self.index + 1 < len(self.nodes_list)- Time Complexity:
O(1)fornext()andhasNext(); the traversal takesO(n), wherenis the number of nodes. - Space Complexity:
O(n)to store the nodes in a list.
This approach doesn’t use extra space to store nodes, it uses the tree structure to mimic the inorder traversal. We modify the tree temporarily and restore it afterwards. Morris Traversal uses threaded binary trees to traverse the tree with space complexity of O(1).
- Use a threaded binary tree to traverse in inorder fashion.
- Keep track of the current position using the tree links and node values.
class BSTIterator:
def __init__(self, root):
self.root = root
self.current = None
self.next_node_value = None
self._morris_setup()
def _morris_setup(self):
# Advance to the first node in the inorder traversal
current = self.root
while current:
if current.left is None:
self.current = current
return
else:
pred = current.left
while pred.right is not None and pred.right != current:
pred = pred.right
if pred.right is None:
pred.right = current
current = current.left
else:
pred.right = None
self.current = current
return
def next(self):
if self.current is None:
return None
next_value = self.current.val
self._morris_setup()
return next_value
def hasNext(self):
return self.current is not None- Time Complexity:
O(1)fornext()on average;O(n)for initial setup which runs everynext(). - Space Complexity:
O(1)because no extra space is used apart from a couple of pointers.
A better approach than both extensive space usage (array storage) and the complex threaded tree traversal is to use a controlled iteration with a stack. This stack holds the ancestors of the current node, which allows for a controlled traversal without altering the tree's structure or using additional space like arrays.
- Use a stack to keep track of the nodes to be visited.
- Traverse left until None, pushing all nodes onto the stack.
next()pops elements from the stack and processes right sub-trees.hasNext()checks if the stack has elements.
class BSTIterator:
def __init__(self, root):
# Stack for the controlled traversal
self.stack = []
# Initialize by going to the leftmost node
self._leftmost_inorder(root)
def _leftmost_inorder(self, node):
# Insert all nodes to reach the leftmost node of a subtree
while node:
self.stack.append(node)
node = node.left
def next(self):
# Node at the top of the stack is the next element
topmost_node = self.stack.pop()
# If there is a right node, perform the leftmost traversal again
if topmost_node.right:
self._leftmost_inorder(topmost_node.right)
return topmost_node.val
def hasNext(self):
# Check if the stack has any more elements
return len(self.stack) > 0- Time Complexity:
O(1)on average fornext()as each node is pushed and popped once. - Space Complexity:
O(h)wherehis the height of the tree (space for stack).
This approach balances between not using additional memory and maintaining reasonable time efficiency across operations.