1586. Binary Search Tree Iterator II 🔒
Problem Description
You need to implement a BSTIterator
class that acts as an iterator for traversing a Binary Search Tree (BST) in in-order fashion. The iterator should support moving both forward and backward through the tree's values.
The class should have the following methods:
-
BSTIterator(TreeNode root)
: Constructor that takes the root of a BST. The iterator's pointer should start at a position before the smallest element (conceptually at position -1). -
hasNext()
: Returnstrue
if there's at least one more element to the right of the current pointer position in the in-order traversal sequence, otherwise returnsfalse
. -
next()
: Moves the pointer one position to the right and returns the value at the new position. This method assumes there's always a valid next element when called. -
hasPrev()
: Returnstrue
if there's at least one element to the left of the current pointer position in the in-order traversal sequence, otherwise returnsfalse
. -
prev()
: Moves the pointer one position to the left and returns the value at the new position. This method assumes there's always a valid previous element when called.
Key points to understand:
- In-order traversal of a BST visits nodes in ascending order (left subtree → root → right subtree)
- The pointer starts before the first element, so the first call to
next()
returns the smallest value - The iterator needs to support bidirectional movement through the sorted sequence
- You can assume
next()
andprev()
will only be called when valid (whenhasNext()
orhasPrev()
returntrue
respectively)
The solution approach stores all BST values in an array using in-order traversal during initialization. It then uses an index pointer i
(starting at -1) to track the current position. Moving forward increments i
and moving backward decrements i
, with the corresponding array value being returned.
Intuition
When we need to iterate through a BST in sorted order while supporting both forward and backward movement, the key insight is that in-order traversal naturally gives us the elements in ascending order.
Think about what happens during in-order traversal: we visit the left subtree first, then the current node, then the right subtree. For a BST, this means we get all values in sorted order. Once we have this sorted sequence, moving forward and backward becomes straightforward - it's just like navigating through an array with an index.
The challenge is supporting bidirectional movement. If we only needed to move forward, we could use a stack-based approach to traverse nodes on-the-fly. But since we also need to move backward (with the prev()
method), keeping track of previously visited nodes becomes necessary.
The simplest approach is to perform a complete in-order traversal during initialization and store all values in an array nums
. This gives us:
- O(1) time complexity for all iterator operations (
hasNext()
,next()
,hasPrev()
,prev()
) - Easy bidirectional movement using a simple index pointer
i
- Clear implementation where
i
tracks our current position in the sorted sequence
We initialize i = -1
to represent a position "before" the first element. This ensures that the first call to next()
moves to index 0 and returns the smallest element, matching the problem's requirement. From there:
next()
incrementsi
and returnsnums[i]
prev()
decrementsi
and returnsnums[i]
hasNext()
checks ifi < len(nums) - 1
hasPrev()
checks ifi > 0
This trade-off of using O(n) space for storing all values upfront gives us the simplicity and efficiency needed for constant-time bidirectional iteration.
Learn more about Stack, Tree, Binary Search Tree and Binary Tree patterns.
Solution Approach
The implementation uses in-order traversal combined with an array-based storage approach to create a bidirectional iterator for the BST.
Data Structures Used:
- Array (
self.nums
): Stores all BST values in sorted order - Index pointer (
self.i
): Tracks the current position in the array
Implementation Steps:
-
Initialization (
__init__
method):- Create an empty array
self.nums
to store all node values - Define a recursive DFS helper function for in-order traversal:
def dfs(root): if root is None: return dfs(root.left) # Visit left subtree self.nums.append(root.val) # Process current node dfs(root.right) # Visit right subtree
- Call
dfs(root)
to populate the array with all values in sorted order - Initialize the pointer
self.i = -1
to position it before the first element
- Create an empty array
-
hasNext()
method:- Check if there are more elements to the right:
self.i < len(self.nums) - 1
- Returns
True
if the current index is less than the last valid index
- Check if there are more elements to the right:
-
next()
method:- Increment the pointer:
self.i += 1
- Return the value at the new position:
return self.nums[self.i]
- Increment the pointer:
-
hasPrev()
method:- Check if there are elements to the left:
self.i > 0
- Returns
True
only if moving back won't go before the first element
- Check if there are elements to the left:
-
prev()
method:- Decrement the pointer:
self.i -= 1
- Return the value at the new position:
return self.nums[self.i]
- Decrement the pointer:
Time and Space Complexity:
- Initialization: O(n) time for traversing all n nodes, O(n) space for storing values
- All iterator operations: O(1) time since we're just accessing array elements and updating an index
- Overall space: O(n) for storing the complete sorted sequence
The beauty of this solution lies in its simplicity - by pre-computing the in-order traversal, we transform the tree navigation problem into simple array indexing, making bidirectional iteration trivial to implement.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through how the BSTIterator works with a small BST:
4 / \ 2 6 / \ \ 1 3 7
Step 1: Initialization
When we create BSTIterator(root)
, the in-order traversal runs:
- Visit left subtree of 4 → goes to 2
- Visit left subtree of 2 → goes to 1
- Visit 1 (leaf) → add 1 to array
- Back to 2 → add 2 to array
- Visit right subtree of 2 → goes to 3
- Visit 3 (leaf) → add 3 to array
- Back to 4 → add 4 to array
- Visit right subtree of 4 → goes to 6
- Visit 6 → add 6 to array
- Visit right subtree of 6 → goes to 7
- Visit 7 (leaf) → add 7 to array
Result: nums = [1, 2, 3, 4, 6, 7]
, i = -1
Step 2: First operations
hasNext() # i=-1, len(nums)=6, returns True (since -1 < 5)
next() # i becomes 0, returns nums[0] = 1
State: i = 0
, pointing at value 1
Step 3: Moving forward
next() # i becomes 1, returns nums[1] = 2
next() # i becomes 2, returns nums[2] = 3
State: i = 2
, pointing at value 3
Step 4: Checking boundaries
hasPrev() # i=2, returns True (since 2 > 0) hasNext() # i=2, returns True (since 2 < 5)
Step 5: Moving backward
prev() # i becomes 1, returns nums[1] = 2 prev() # i becomes 0, returns nums[0] = 1 hasPrev() # i=0, returns False (since 0 is not > 0)
Step 6: Moving to the end
next() # i becomes 1, returns 2
next() # i becomes 2, returns 3
next() # i becomes 3, returns 4
next() # i becomes 4, returns 6
next() # i becomes 5, returns 7
hasNext() # i=5, returns False (since 5 is not < 5)
The iterator successfully navigates bidirectionally through the BST values in sorted order, with the pointer tracking our position in the pre-computed array.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9
10class BSTIterator:
11 def __init__(self, root: Optional[TreeNode]) -> None:
12 """
13 Initialize the BST iterator with the root of a binary search tree.
14 Performs an in-order traversal to store all values in sorted order.
15
16 Args:
17 root: The root node of the binary search tree
18 """
19 # Store all BST values in sorted order (in-order traversal)
20 self.values = []
21
22 # Helper function to perform in-order traversal
23 def inorder_traversal(node: Optional[TreeNode]) -> None:
24 """
25 Perform in-order traversal of the BST to collect values in sorted order.
26
27 Args:
28 node: Current node being visited
29 """
30 if node is None:
31 return
32
33 # Traverse left subtree
34 inorder_traversal(node.left)
35 # Visit current node
36 self.values.append(node.val)
37 # Traverse right subtree
38 inorder_traversal(node.right)
39
40 # Populate the values list with BST elements
41 inorder_traversal(root)
42
43 # Initialize pointer to track current position in the iterator
44 # Starting at -1 means we haven't accessed any element yet
45 self.current_index = -1
46
47 def hasNext(self) -> bool:
48 """
49 Check if there is a next element in the iterator.
50
51 Returns:
52 True if there is a next element, False otherwise
53 """
54 return self.current_index < len(self.values) - 1
55
56 def next(self) -> int:
57 """
58 Move the iterator forward and return the next element.
59
60 Returns:
61 The next value in the BST traversal
62 """
63 self.current_index += 1
64 return self.values[self.current_index]
65
66 def hasPrev(self) -> bool:
67 """
68 Check if there is a previous element in the iterator.
69
70 Returns:
71 True if there is a previous element, False otherwise
72 """
73 return self.current_index > 0
74
75 def prev(self) -> int:
76 """
77 Move the iterator backward and return the previous element.
78
79 Returns:
80 The previous value in the BST traversal
81 """
82 self.current_index -= 1
83 return self.values[self.current_index]
84
85
86# Your BSTIterator object will be instantiated and called as such:
87# obj = BSTIterator(root)
88# param_1 = obj.hasNext()
89# param_2 = obj.next()
90# param_3 = obj.hasPrev()
91# param_4 = obj.prev()
92
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class BSTIterator {
17 // List to store the inorder traversal of the BST
18 private List<Integer> inorderValues;
19 // Current index pointer for iteration
20 private int currentIndex;
21
22 /**
23 * Initializes the iterator with the root of a BST.
24 * Performs inorder traversal to flatten the BST into a sorted list.
25 *
26 * @param root The root node of the binary search tree
27 */
28 public BSTIterator(TreeNode root) {
29 inorderValues = new ArrayList<>();
30 currentIndex = -1;
31 performInorderTraversal(root);
32 }
33
34 /**
35 * Checks if there exists a next element in the iteration.
36 *
37 * @return true if there is a next element, false otherwise
38 */
39 public boolean hasNext() {
40 return currentIndex < inorderValues.size() - 1;
41 }
42
43 /**
44 * Moves the pointer to the next element and returns its value.
45 *
46 * @return The value of the next element in the inorder sequence
47 */
48 public int next() {
49 currentIndex++;
50 return inorderValues.get(currentIndex);
51 }
52
53 /**
54 * Checks if there exists a previous element in the iteration.
55 *
56 * @return true if there is a previous element, false otherwise
57 */
58 public boolean hasPrev() {
59 return currentIndex > 0;
60 }
61
62 /**
63 * Moves the pointer to the previous element and returns its value.
64 *
65 * @return The value of the previous element in the inorder sequence
66 */
67 public int prev() {
68 currentIndex--;
69 return inorderValues.get(currentIndex);
70 }
71
72 /**
73 * Helper method to perform inorder traversal of the BST.
74 * Inorder traversal visits nodes in ascending order for a BST.
75 *
76 * @param node The current node being processed
77 */
78 private void performInorderTraversal(TreeNode node) {
79 // Base case: if node is null, return
80 if (node == null) {
81 return;
82 }
83
84 // Traverse the left subtree
85 performInorderTraversal(node.left);
86
87 // Process the current node
88 inorderValues.add(node.val);
89
90 // Traverse the right subtree
91 performInorderTraversal(node.right);
92 }
93}
94
95/**
96 * Your BSTIterator object will be instantiated and called as such:
97 * BSTIterator obj = new BSTIterator(root);
98 * boolean param_1 = obj.hasNext();
99 * int param_2 = obj.next();
100 * boolean param_3 = obj.hasPrev();
101 * int param_4 = obj.prev();
102 */
103
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12
13/**
14 * BST Iterator class that supports bidirectional iteration
15 * Performs in-order traversal of BST and stores values in a vector
16 */
17class BSTIterator {
18public:
19 /**
20 * Constructor: Initializes the iterator with the root of BST
21 * @param root: Root node of the binary search tree
22 */
23 BSTIterator(TreeNode* root) {
24 // Perform in-order traversal to populate the values vector
25 inorderTraversal(root);
26 // Store the size of the values vector
27 size = values.size();
28 }
29
30 /**
31 * Checks if there is a next element in the iteration
32 * @return true if next element exists, false otherwise
33 */
34 bool hasNext() {
35 return currentIndex < size - 1;
36 }
37
38 /**
39 * Moves to and returns the next element in the iteration
40 * @return The next element value
41 */
42 int next() {
43 return values[++currentIndex];
44 }
45
46 /**
47 * Checks if there is a previous element in the iteration
48 * @return true if previous element exists, false otherwise
49 */
50 bool hasPrev() {
51 return currentIndex > 0;
52 }
53
54 /**
55 * Moves to and returns the previous element in the iteration
56 * @return The previous element value
57 */
58 int prev() {
59 return values[--currentIndex];
60 }
61
62private:
63 vector<int> values; // Stores all BST values in sorted order
64 int currentIndex = -1; // Current position in the values vector (-1 means before first element)
65 int size; // Total number of elements in the BST
66
67 /**
68 * Helper function to perform in-order traversal of the BST
69 * @param node: Current node being processed
70 */
71 void inorderTraversal(TreeNode* node) {
72 // Base case: if node is null, return
73 if (!node) {
74 return;
75 }
76
77 // Traverse left subtree
78 inorderTraversal(node->left);
79
80 // Process current node (add to values vector)
81 values.push_back(node->val);
82
83 // Traverse right subtree
84 inorderTraversal(node->right);
85 }
86};
87
88/**
89 * Your BSTIterator object will be instantiated and called as such:
90 * BSTIterator* obj = new BSTIterator(root);
91 * bool param_1 = obj->hasNext();
92 * int param_2 = obj->next();
93 * bool param_3 = obj->hasPrev();
94 * int param_4 = obj->prev();
95 */
96
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15// Array to store the values from BST in sorted order
16let sortedValues: number[] = [];
17// Total number of elements in the BST
18let totalElements: number = 0;
19// Current position of the iterator
20let currentIndex: number = -1;
21
22/**
23 * Initializes the BST iterator with the root of the BST
24 * Performs an in-order traversal to collect all values in sorted order
25 * @param root - The root node of the binary search tree
26 */
27function BSTIterator(root: TreeNode | null): void {
28 // Reset the global variables
29 sortedValues = [];
30 currentIndex = -1;
31
32 /**
33 * Helper function to perform in-order traversal
34 * Traverses left subtree, visits node, then traverses right subtree
35 * This ensures values are collected in ascending order for BST
36 */
37 const inOrderTraversal = (node: TreeNode | null): void => {
38 if (!node) {
39 return;
40 }
41
42 // Traverse left subtree
43 inOrderTraversal(node.left);
44 // Visit current node - add value to sorted array
45 sortedValues.push(node.val);
46 // Traverse right subtree
47 inOrderTraversal(node.right);
48 };
49
50 // Perform the traversal starting from root
51 inOrderTraversal(root);
52 // Store the total number of elements
53 totalElements = sortedValues.length;
54}
55
56/**
57 * Checks if there is a next element in the iteration
58 * @returns true if there are more elements to iterate, false otherwise
59 */
60function hasNext(): boolean {
61 // Check if current index is before the last element
62 return currentIndex < totalElements - 1;
63}
64
65/**
66 * Moves the iterator forward and returns the next element
67 * @returns The next value in the BST (in ascending order)
68 */
69function next(): number {
70 // Increment the index and return the corresponding value
71 currentIndex++;
72 return sortedValues[currentIndex];
73}
74
75/**
76 * Checks if there is a previous element in the iteration
77 * @returns true if we can move backwards, false otherwise
78 */
79function hasPrev(): boolean {
80 // Check if current index is after the first element
81 return currentIndex > 0;
82}
83
84/**
85 * Moves the iterator backward and returns the previous element
86 * @returns The previous value in the BST (in ascending order)
87 */
88function prev(): number {
89 // Decrement the index and return the corresponding value
90 currentIndex--;
91 return sortedValues[currentIndex];
92}
93
Time and Space Complexity
Time Complexity:
- Initialization (
__init__
):O(n)
wheren
is the number of nodes in the binary search tree. The constructor performs an in-order traversal using DFS to visit every node exactly once and store its value in thenums
list. hasNext()
method:O(1)
- Simply compares the current index with the length of the array.next()
method:O(1)
- Increments the index and returns the value at that position in the array.hasPrev()
method:O(1)
- Simply checks if the current index is greater than 0.prev()
method:O(1)
- Decrements the index and returns the value at that position in the array.
Space Complexity:
O(n)
wheren
is the number of nodes in the binary search tree. Thenums
list stores all node values from the BST after the in-order traversal. Additionally, the recursive DFS call stack can go up toO(h)
depth whereh
is the height of the tree, but since we're already usingO(n)
space for the array, the overall space complexity remainsO(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Initial Pointer Position
A common mistake is initializing the pointer at index 0 instead of -1. This would cause the first next()
call to skip the smallest element.
Wrong Implementation:
def __init__(self, root):
self.values = []
# ... perform traversal ...
self.current_index = 0 # WRONG: Starting at first element
Why it's wrong: The problem states the pointer should start "before the smallest element". If we start at 0, the first next()
call would move to index 1, returning the second smallest value instead of the smallest.
Correct Implementation:
def __init__(self, root):
self.values = []
# ... perform traversal ...
self.current_index = -1 # CORRECT: Starting before first element
2. Off-by-One Errors in Boundary Checks
Another frequent error occurs in the hasPrev()
and hasNext()
methods with incorrect boundary conditions.
Wrong Implementation:
def hasNext(self):
return self.current_index < len(self.values) # WRONG: allows going past last element
def hasPrev(self):
return self.current_index >= 0 # WRONG: allows going before first element when at index 0
Why it's wrong:
- For
hasNext()
: When at the last element (indexlen-1
), this would incorrectly returnTrue
- For
hasPrev()
: When at index 0 (first element), we shouldn't be able to go back further
Correct Implementation:
def hasNext(self):
return self.current_index < len(self.values) - 1 # Can move forward if not at last element
def hasPrev(self):
return self.current_index > 0 # Can move backward only if past the first element
3. Memory Optimization Misconception
Some might try to optimize memory by not storing all values upfront, instead traversing the tree on each operation. While this saves space, it violates the O(1) time complexity requirement for iterator operations.
Inefficient Alternative:
def next(self):
# Trying to find next element by traversing tree each time
# This would be O(h) or O(n) time complexity, not O(1)
current = self.find_current_node()
successor = self.find_inorder_successor(current)
return successor.val
Why the array approach is better: Although it uses O(n) space, it guarantees O(1) time for all iterator operations, which is typically the expected behavior for iterators.
4. Not Handling Empty Trees
Forgetting to handle the edge case where the BST is empty (root is None).
Potential Issue:
def hasNext(self):
# This works fine even for empty trees since len([]) - 1 = -1
# and -1 < -1 is False
return self.current_index < len(self.values) - 1
While the current implementation handles empty trees correctly (empty array means hasNext()
and hasPrev()
always return False
), it's good practice to be aware of this edge case and ensure your implementation handles it properly.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Want a Structured Path to Master System Design Too? Don’t Miss This!