BST Inorder Predecessor in DSA Rust - Time & Space Complexity
We want to understand how long it takes to find the inorder predecessor in a Binary Search Tree (BST).
This helps us know how the search time grows as the tree gets bigger.
Analyze the time complexity of the following code snippet.
fn inorder_predecessor(root: &Option>, key: i32) -> Option {
let mut predecessor = None;
let mut current = root;
while let Some(node) = current {
if key <= node.val {
current = &node.left;
} else {
predecessor = Some(node.val);
current = &node.right;
}
}
predecessor
}
This code finds the inorder predecessor of a given key in a BST by traversing the tree.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: A while loop that moves down the tree nodes.
- How many times: At most once per level of the tree, until it reaches a leaf or finds the predecessor.
As the tree grows taller, the number of steps to find the predecessor grows roughly with the height of the tree.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 3 to 4 steps (tree height) |
| 100 | About 6 to 7 steps |
| 1000 | About 9 to 10 steps |
Pattern observation: The steps grow slowly as the tree height increases, not directly with total nodes.
Time Complexity: O(h)
This means the time depends on the height of the tree, which is how many levels it has.
[X] Wrong: "Finding the inorder predecessor always takes O(n) time because we might check every node."
[OK] Correct: We only follow one path down the tree, not all nodes, so the time depends on the height, not total nodes.
Understanding this helps you explain how tree height affects search times, a key skill for working with BSTs and balanced trees.
"What if the BST is perfectly balanced? How would the time complexity change?"
Practice
Solution
Step 1: Understand BST property
In a BST, left subtree nodes are smaller and right subtree nodes are larger than the current node.Step 2: Define inorder predecessor
The inorder predecessor is the node visited just before the current node in an inorder traversal, which means it has the largest value smaller than the current node.Final Answer:
The node with the largest value smaller than the given node -> Option AQuick Check:
Inorder predecessor = largest smaller value [OK]
- Confusing predecessor with successor
- Choosing child nodes directly without checking values
- Thinking predecessor is always left child
Solution
Step 1: Identify left subtree traversal
The inorder predecessor is the rightmost node in the left subtree, so start from node.left.Step 2: Traverse to rightmost node
Keep moving to the right child until no more right child exists to find the largest node in left subtree.Final Answer:
let mut pred = node.left.as_ref(); while let Some(right) = pred.unwrap().right.as_ref() { pred = Some(right); } -> Option CQuick Check:
Traverse left subtree rightmost node = let mut pred = node.left.as_ref(); while let Some(right) = pred.unwrap().right.as_ref() { pred = Some(right); } [OK]
- Starting from right subtree instead of left
- Not looping to the rightmost node
- Using clone instead of reference traversal
20
/ \
10 30
\ \
15 40
Solution
Step 1: Locate node 15 and its left subtree
Node 15 has no left child, so we look for the ancestor smaller than 15.Step 2: Find largest ancestor smaller than 15
10 is the parent of 15 and smaller than 15, so 10 is the inorder predecessor.Final Answer:
10 -> Option DQuick Check:
Largest smaller ancestor = 10 [OK]
- Choosing node itself as predecessor
- Choosing larger ancestor incorrectly
- Ignoring no left subtree case
fn inorder_predecessor(node: &Option) -> Option<i32> {
let mut current = node.as_ref()?.left.as_ref();
while current?.right.is_some() {
current = current?.right.as_ref();
}
current.map(|n| n.val)
}
Solution
Step 1: Analyze use of '?' operator in loop condition
The '?' operator unwraps Option or returns None; using it inside while condition causes panic if None.Step 2: Identify safe way to traverse Option references
Must use pattern matching or if let to safely unwrap Options instead of '?' in loop condition.Final Answer:
Using '?' operator on Option without handling None case -> Option AQuick Check:
Unsafe '?' in loop condition causes panic [OK]
- Using '?' inside loops causing runtime panic
- Ignoring None cases in Option traversal
- Confusing left and right subtree
Solution
Step 1: Understand no left child case
If node has no left child, predecessor is not in left subtree but among ancestors.Step 2: Find nearest ancestor where node is in right subtree
Traverse up until you find an ancestor whose right child is also an ancestor of the node; that ancestor is the predecessor.Final Answer:
Traverse up the tree to find the nearest ancestor whose right child is also an ancestor of the node -> Option BQuick Check:
No left child -> find right-side ancestor [OK]
- Returning nodes from right subtree incorrectly
- Always returning root node
- Ignoring ancestor traversal
Ready to prove it?
Pass the test and this pattern moves unknown
