Facebook Pixel

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:

  1. 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).

  2. hasNext(): Returns true if there's at least one more element to the right of the current pointer position in the in-order traversal sequence, otherwise returns false.

  3. 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.

  4. hasPrev(): Returns true if there's at least one element to the left of the current pointer position in the in-order traversal sequence, otherwise returns false.

  5. 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() and prev() will only be called when valid (when hasNext() or hasPrev() return true 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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() increments i and returns nums[i]
  • prev() decrements i and returns nums[i]
  • hasNext() checks if i < len(nums) - 1
  • hasPrev() checks if i > 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:

  1. 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
  2. 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
  3. next() method:

    • Increment the pointer: self.i += 1
    • Return the value at the new position: return self.nums[self.i]
  4. 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
  5. prev() method:

    • Decrement the pointer: self.i -= 1
    • Return the value at the new position: return self.nums[self.i]

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 Evaluator

Example 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) where n 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 the nums 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) where n is the number of nodes in the binary search tree. The nums list stores all node values from the BST after the in-order traversal. Additionally, the recursive DFS call stack can go up to O(h) depth where h is the height of the tree, but since we're already using O(n) space for the array, the overall space complexity remains O(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 (index len-1), this would incorrectly return True
  • 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More