BST Inorder Predecessor in DSA Python - Time & Space Complexity
We want to understand how long it takes to find the inorder predecessor in a Binary Search Tree (BST).
Specifically, how the time grows as the tree gets bigger.
Analyze the time complexity of the following code snippet.
def inorder_predecessor(root, node):
if node.left:
pred = node.left
while pred.right:
pred = pred.right
return pred
pred = None
curr = root
while curr:
if node.val <= curr.val:
curr = curr.left
else:
pred = curr
curr = curr.right
return pred
This code finds the inorder predecessor of a given node in a BST.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Traversing down the tree nodes using while loops.
- How many times: At most, one path from root to a leaf or from node's left child down to the rightmost leaf.
As the BST grows, the longest path to traverse grows with the tree height.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | Up to 4-5 steps (tree height) |
| 100 | Up to 7-8 steps |
| 1000 | Up to 10-11 steps |
Pattern observation: The number of steps grows roughly with the height of the tree, not the total number of nodes.
Time Complexity: O(h)
This means the time depends on the height of the BST, which is the longest path from root to leaf.
[X] Wrong: "Finding the inorder predecessor always takes O(n) time because we might check all nodes."
[OK] Correct: We only follow one path down the tree, never visiting all nodes, so the time depends on the tree height, not total nodes.
Understanding this helps you explain how tree structure affects search times, a key skill in many coding challenges.
"What if the BST is balanced vs completely skewed? How would the time complexity change?"
Practice
Solution
Step 1: Understand inorder traversal order
Inorder traversal visits nodes in ascending order for BST, so predecessor is the previous node in this order.Step 2: Define inorder predecessor
The inorder predecessor is the node with the largest value smaller than the current node's value.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
- Thinking predecessor is always left child
- Assuming predecessor is the parent node
Solution
Step 1: Identify inorder predecessor logic for left subtree
If node has left child, predecessor is the rightmost node in left subtree.Step 2: Check code correctness
Code D moves to left child, then goes right as far as possible, correctly finding predecessor.Final Answer:
Code snippet D correctly finds the inorder predecessor -> Option DQuick Check:
Left subtree's rightmost node = predecessor [OK]
- Going right subtree instead of left
- Going left repeatedly instead of right in left subtree
- Returning left child directly without traversal
20
/ \
10 30
\ \
15 40
Solution
Step 1: Locate node 15 and check left subtree
Node 15 has no left child, so predecessor is ancestor smaller than 15.Step 2: Find nearest ancestor smaller than 15
Ancestor 10 is smaller than 15 and is the closest on path to root.Final Answer:
10 -> Option CQuick Check:
Predecessor of 15 is 10 [OK]
- Choosing 20 which is larger
- Choosing node itself as predecessor
- Assuming predecessor is None without checking ancestors
def inorder_predecessor(root, node):
if node.left:
temp = node.left
while temp.left:
temp = temp.left
return temp
else:
predecessor = None
while root:
if node.val > root.val:
predecessor = root
root = root.right
else:
root = root.left
return predecessor
Solution
Step 1: Analyze left subtree traversal
In left subtree, predecessor is rightmost node, so loop should go right, not left.Step 2: Identify correct traversal direction
Changing while temp.left to while temp.right fixes the bug.Final Answer:
The while loop should check temp.right, not temp.left -> Option BQuick Check:
Predecessor in left subtree = rightmost node [OK]
- Looping left instead of right in left subtree
- Ignoring else block logic
- Not returning predecessor correctly
Solution
Step 1: Understand no left child case
If node has no left child, predecessor is an ancestor smaller than node.Step 2: Use root traversal to track predecessor
Traverse from root, update predecessor when node value is greater than root's value.Step 3: Return last tracked smaller ancestor
This gives the inorder predecessor correctly.Final Answer:
Traverse from root, track the last node smaller than current node -> Option AQuick Check:
No left child -> track smaller ancestor [OK]
- Returning parent without checking value
- Returning left child when none exists
- Trying to find predecessor in left subtree when none exists
Ready to prove it?
Pass the test and this pattern moves unknown
