BST Inorder Predecessor in DSA Go - Time & Space Complexity
We want to understand how long it takes to find the inorder predecessor in a Binary Search Tree (BST).
The question is: how does the time grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
func inorderPredecessor(root *Node, key int) *Node {
var predecessor *Node
current := root
for current != nil {
if key <= current.val {
current = current.left
} else {
predecessor = current
current = current.right
}
}
return predecessor
}
This code finds the inorder predecessor of a given key in a BST by traversing the tree from the root.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Traversing down the tree nodes using a loop.
- How many times: At most once per level of the tree, moving to left or right child each step.
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 | Up to 4 steps (if tree height ~4) |
| 100 | Up to 7 steps (if tree height ~7) |
| 1000 | Up to 10 steps (if tree height ~10) |
Pattern observation: The number of steps grows with the tree height, which depends on how balanced the tree is.
Time Complexity: O(h)
This means the time to find the inorder predecessor grows with the height of the BST, not the total number of nodes.
[X] Wrong: "Finding the inorder predecessor always takes time proportional to the total number of nodes in the tree."
[OK] Correct: The search only follows one path down the tree, so it depends on the height, not all nodes.
Understanding how tree height affects search time helps you explain and optimize tree operations clearly in interviews.
"What if the BST is perfectly balanced? How would the time complexity change?"
Practice
Solution
Step 1: Understand inorder traversal in BST
Inorder traversal visits nodes in ascending order of their values.Step 2: Define inorder predecessor
The inorder predecessor of a node is the node that comes just before it in this ascending order, meaning it has the largest value smaller than the node.Final Answer:
The node with the largest value smaller than the given node -> Option BQuick Check:
Inorder predecessor = largest smaller value [OK]
- Confusing predecessor with successor
- Thinking predecessor is always left child
- Assuming predecessor is the parent node
Node?Solution
Step 1: Analyze function parameters and return type
The function needs the BST root pointer and the key to find predecessor for, and returns a pointer to the predecessor node.Step 2: Match with options
func inorderPredecessor(root *Node, key int) *Node matches this: it takes*Noderoot,intkey, and returns*Node.Final Answer:
func inorderPredecessor(root *Node, key int) *Node -> Option AQuick Check:
Pointer root and key input, pointer node output [OK]
- Passing root by value instead of pointer
- Returning node value instead of node pointer
- Missing key parameter to identify target node
type Node struct {
Val int
Left, Right *Node
}
func inorderPredecessor(root *Node, key int) *Node {
var predecessor *Node
curr := root
for curr != nil {
if key <= curr.Val {
curr = curr.Left
} else {
predecessor = curr
curr = curr.Right
}
}
return predecessor
}
func main() {
root := &Node{Val: 20}
root.Left = &Node{Val: 10}
root.Right = &Node{Val: 30}
root.Left.Left = &Node{Val: 5}
root.Left.Right = &Node{Val: 15}
pred := inorderPredecessor(root, 15)
if pred != nil {
fmt.Println(pred.Val)
} else {
fmt.Println("No predecessor")
}
}Solution
Step 1: Trace the inorderPredecessor function for key=15
Start at root (20). Since 15 <= 20, move left to 10. Since 15 > 10, set predecessor=10, move right to 15. Since 15 <= 15, move left (nil). Loop ends.Step 2: Return predecessor node value
Predecessor is node with value 10, so print 10.Final Answer:
10 -> Option DQuick Check:
Largest smaller than 15 is 10 [OK]
- Returning the key itself as predecessor
- Confusing predecessor with successor
- Not updating predecessor correctly when moving right
key in a BST. What is the bug?
func inorderPredecessor(root *Node, key int) *Node {
var predecessor *Node
curr := root
for curr != nil {
if key < curr.Val {
curr = curr.Left
} else {
predecessor = curr
curr = curr.Right
}
}
return predecessor
}Solution
Step 1: Analyze the condition in the loop
The current condition is key < curr.Val, which skips updating predecessor when key equals curr.Val.Step 2: Correct the condition to include equality
Changing to key <= curr.Val ensures that when key equals curr.Val, we move left without updating predecessor, which is correct for inorder predecessor logic.Final Answer:
The condition should be key <= curr.Val to handle equal keys correctly -> Option CQuick Check:
Use <= to handle equal keys properly [OK]
- Using < instead of <= causes wrong predecessor for equal keys
- Resetting predecessor inside loop loses previous valid value
- Returning curr instead of predecessor returns wrong node
n efficiently without starting from the root? Choose the best approach.Solution
Step 1: Check if node has left subtree
Ifnhas a left child, the predecessor is the rightmost node in that left subtree.Step 2: If no left child, move up using parent pointers
Move up until you find a node that is the right child of its parent; that parent is the predecessor.Final Answer:
If n has left child, find rightmost in left subtree; else move up until node is right child -> Option AQuick Check:
Use left subtree or climb parents to find predecessor [OK]
- Starting from root wastes time
- Confusing predecessor with successor logic
- Returning parent directly without checks
Ready to prove it?
Pass the test and this pattern moves unknown
