Topics › Binary Search Trees › Practice Problems
Inorder Successor/Predecessor in BST
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the root of a binary search tree and a key. Return two values:
- the inorder predecessor: the largest key in the tree that is strictly smaller than
key, - the inorder successor: the smallest key in the tree that is strictly larger than
key.
If either one does not exist, report -1 for it. The result is {predecessor, successor}. When key is in the tree these are simply its neighbours in the in-order (sorted) sequence; the definition above also works when key is not in the tree, which some versions of the problem allow. Keys are positive here, so -1 cannot be confused with a real key. Constraints: up to 10^5 nodes.
All examples use this tree:
5
/ \
2 10
/ \ / \
1 4 7 12
in-order: 1 2 4 5 7 10 12
Example 1
key = 10
answer = {7, 12} // neighbours of 10 in the sorted sequence
Example 2
key = 4
answer = {2, 5} // the successor 5 is an ancestor of 4, not a descendant
Example 3
key = 6
answer = {5, 7} // 6 is not in the tree; 5 is the largest key below it, 7 the smallest above
Approach 1: Brute force, read the sorted sequence
Intuition. The in-order traversal of a BST lists its keys in ascending order. Once we have that list, the predecessor is the last key below key and the successor is the first key above it. A single scan finds both. This ignores the tree’s shape entirely, so it does a full traversal even though the answer sits near one path.
Algorithm.
- Collect the keys with an in-order traversal.
- Scan left to right. Every key below
keyoverwritespred, so the last one wins. The first key abovekeyissucc, and the scan can stop there. - Return
{pred, succ}, with-1for anything not found.
static int[] bruteForce(TreeNode root, int key) {
List<Integer> sorted = new ArrayList<>();
collectInorder(root, sorted);
int pred = NONE, succ = NONE;
for (int v : sorted) {
if (v < key) pred = v; // keeps overwriting: the last one below key wins
else if (v > key) { succ = v; break; } // the first one above key is the answer
}
return new int[]{pred, succ};
}
static void collectInorder(TreeNode node, List<Integer> out) {
if (node == null) return;
collectInorder(node.left, out);
out.add(node.val);
collectInorder(node.right, out);
}
Complexity. Time O(n) for the traversal. Space O(n) for the list, plus O(h) recursion.
Approach 2: Optimal, one root-to-leaf walk per answer
Intuition. Searching for the successor is a binary search for “the smallest key that is greater than key”. Stand at a node cur:
- If
cur.val > key, thencurqualifies as a successor. A better (smaller) candidate can still exist, but only among keys smaller thancur.valthat are still abovekey, and those live incur’s left subtree. So recordcur.valand go left. - If
cur.val <= key, thencurdoes not qualify, and neither does anything in its left subtree, since those keys are even smaller. Only the right subtree can help, so go right without recording.
Every time we record, we then move into the left subtree of the recorded node, so each new candidate is smaller than the previous one. When we fall off the tree, the last recorded value is the smallest key above key. The predecessor is the mirror image: record when cur.val < key and go right; otherwise go left.
The textbook version needs parent pointers: “if the node has a right subtree, take its leftmost node; otherwise climb to the first ancestor of which the node is in the left subtree”. The walk from the root covers both cases. In the second case the ancestor was recorded on the way down, and in the first case the walk continues into the right subtree and records its leftmost path.
Algorithm.
successor: start at the root withsucc = -1. Ifcur.val > key, setsucc = cur.valand go left; else go right. Stop atnull.predecessor: start at the root withpred = -1. Ifcur.val < key, setpred = cur.valand go right; else go left.- Return
{predecessor, successor}.
static int[] optimal(TreeNode root, int key) {
return new int[]{predecessor(root, key), successor(root, key)};
}
static int successor(TreeNode root, int key) {
int succ = NONE;
TreeNode cur = root;
while (cur != null) {
if (cur.val > key) {
succ = cur.val; // a candidate; anything smaller that still beats key is on the left
cur = cur.left;
} else {
cur = cur.right; // cur and its whole left subtree are <= key
}
}
return succ;
}
static int predecessor(TreeNode root, int key) {
int pred = NONE;
TreeNode cur = root;
while (cur != null) {
if (cur.val < key) {
pred = cur.val; // a candidate; anything larger that is still below key is on the right
cur = cur.right;
} else {
cur = cur.left; // cur and its whole right subtree are >= key
}
}
return pred;
}
Dry run for the successor of 4:
| cur | cur.val > 4? | succ | move |
|---|---|---|---|
| 5 | yes | 5 | left to 2 |
| 2 | no | 5 | right to 4 |
| 4 | no | 5 | right to null |
The answer is 5, an ancestor, which the “leftmost node of the right subtree” rule alone would miss because 4 has no right subtree.
And for the predecessor of 10:
| cur | cur.val < 10? | pred | move |
|---|---|---|---|
| 5 | yes | 5 | right to 10 |
| 10 | no | 5 | left to 7 |
| 7 | yes | 7 | right to null |
The answer is 7.
Complexity. Time O(h): each walk descends one level per step, O(log n) for a balanced tree and O(n) for a chain. Space O(1): two loops with one pointer each.
These two walks are exactly the “ceil” and “floor” searches on a BST, with strict comparisons in place of >= and <=. Because the comparison is strict, a node whose key equals key is skipped automatically, which is what makes the method work whether or not key is present.
Edge cases and pitfalls
- Smallest and largest keys. The smallest key has no predecessor and the largest has no successor; the walks never record anything for them and return
-1. - Key not in the tree.
key = 6gives{5, 7}. Strict comparisons mean no special handling is needed. - Successor is an ancestor.
key = 4and, in the test tree[20, 8, 22, 4, 12, null, null, null, null, 10, 14],key = 14whose successor20is two levels up. Implementations that only look at the right subtree fail these. - Empty tree or single node. An empty tree returns
{-1, -1}for any key, and so does a one-node tree when the key equals that node. - Sentinel collisions.
-1works only because keys are positive. If keys can be negative, returnIntegerwithnull, or alongsentinel outside theintrange. - Duplicates. Strict comparisons skip equal keys, so a tree with repeated keys still returns the nearest strictly smaller and strictly larger values.
Related problems
- Floor and Ceil in a BST is the same walk with non-strict comparisons.
- Floor in a Binary Search Tree is the predecessor walk with
<=. - Search in a Binary Search Tree is the base one-path walk.
- Kth Smallest and Largest element in BST also reasons about positions in the in-order sequence.
- BST iterator produces successors one after another in amortised
O(1). - LCA in BST uses the same “compare and pick a side” walk for two keys at once.