BST Inorder Successor in DSA Rust - Time & Space Complexity
We want to understand how long it takes to find the next bigger value in a binary search tree (BST).
How does the time needed grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
fn inorder_successor(root: &Option>, p: &TreeNode) -> Option> {
let mut successor = None;
let mut current = root;
while let Some(node) = current {
if p.val < node.val {
successor = Some(node.clone());
current = &node.left;
} else {
current = &node.right;
}
}
successor
}
This code finds the inorder successor of a node in a BST by walking down the tree.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Traversing down the tree nodes one by one.
- How many times: At most once per level of the tree, from root to leaf.
As the tree grows taller, the number of steps to find the successor grows roughly with the height.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 (small tree) | About 3-4 steps (height ~3-4) |
| 100 (medium tree) | About 7 steps (height ~7) |
| 1000 (large tree) | About 10 steps (height ~10) |
Pattern observation: The steps grow with the tree height, which is usually much smaller than total nodes.
Time Complexity: O(h)
This means the time depends on the tree height, not the total number of nodes.
[X] Wrong: "Finding the successor always takes time proportional to the total number of nodes."
[OK] Correct: We only move down one path from root to leaf, so the time depends on height, not total nodes.
Understanding how tree height affects search time helps you explain and optimize tree operations clearly.
"What if the BST is unbalanced and looks like a linked list? How would the time complexity change?"
Practice
Solution
Step 1: Understand inorder traversal order
Inorder traversal visits nodes in ascending order of their keys in a BST.Step 2: Define inorder successor
The inorder successor of a node is the next node visited after it in inorder traversal, which means the smallest node with a key greater than the current node.Final Answer:
The node with the smallest key greater than the given node's key -> Option AQuick Check:
Inorder successor = next bigger node [OK]
- Confusing successor with predecessor
- Thinking successor is always right child
- Ignoring BST property in definition
Solution
Step 1: Understand Rust ownership and references
BST nodes are usually wrapped in Option to allow null and heap allocation. Passing references avoids ownership transfer.Step 2: Analyze function signature correctness
Option<&TreeNode> as return type allows returning a reference to a node or None. Input parameters as references to avoid moving ownership.Final Answer:
fn inorder_successor(root: &Option, p: &TreeNode) -> Option<&TreeNode> -> Option AQuick Check:
Use references and Option for safe BST traversal [OK]
- Passing nodes by value causing ownership errors
- Returning owned nodes instead of references
- Using mutable references unnecessarily
struct TreeNode {
val: i32,
left: Option,
right: Option,
}
fn inorder_successor(root: &Option, p_val: i32) -> Option<i32> {
let mut successor = None;
let mut current = root;
while let Some(node) = current {
if p_val < node.val {
successor = Some(node.val);
current = &node.left;
} else {
current = &node.right;
}
}
successor
}
fn main() {
let root = Some(Box::new(TreeNode {
val: 20,
left: Some(Box::new(TreeNode {
val: 10,
left: None,
right: Some(Box::new(TreeNode {
val: 15,
left: None,
right: None,
})),
})),
right: Some(Box::new(TreeNode {
val: 30,
left: None,
right: None,
})),
}));
let result = inorder_successor(&root, 15);
println!("{:?}", result);
}Solution
Step 1: Trace the search for successor of node with value 15
Start at root (20). Since 15 < 20, successor = 20, move left to node 10. 15 > 10, move right to node 15.Step 2: Continue traversal and finalize successor
At node 15, 15 == 15, move right which is None. Loop ends. Last successor recorded is 20.Final Answer:
Some(20) -> Option BQuick Check:
Next bigger than 15 is 20 [OK]
- Returning the node itself as successor
- Confusing left and right subtree traversal
- Not updating successor when moving left
fn inorder_successor(root: &Option, p_val: i32) -> Option<i32> {
let mut successor = None;
let mut current = root;
while let Some(node) = current {
if p_val <= node.val {
successor = Some(node.val);
current = &node.left;
} else {
current = &node.right;
}
}
successor
}Solution
Step 1: Analyze condition p_val <= node.val
Using <= means successor updates even when p_val equals node.val, which incorrectly sets successor to the node itself.Step 2: Correct condition for successor update
Successor should update only when p_val < node.val to find the next bigger node, not the node itself.Final Answer:
Using <= instead of < causes incorrect successor when p_val equals node.val -> Option DQuick Check:
Use < for successor update, not <= [OK]
- Confusing < and <= in comparisons
- Ignoring edge case when p_val equals node.val
- Not testing with equal values
p efficiently using this extra pointer?Solution
Step 1: Check if node has right subtree
Ifphas a right child, the successor is the leftmost node in that right subtree.Step 2: If no right child, use parent pointer
Move up the tree using parent pointers until you find a node which is the left child of its parent; that parent is the successor.Final Answer:
Ifphas right child, successor is leftmost node in right subtree; else go up using parent until node is left child -> Option CQuick Check:
Use right subtree or climb parents to find successor [OK]
- Ignoring parent pointer advantage
- Assuming successor is always right child
- Performing full traversal unnecessarily
Ready to prove it?
Pass the test and this pattern moves unknown
