Topics › Binary Search Trees › Practice Problems
Two Sum In BST | Check if there exists a pair with Sum K
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the root of a binary search tree and an integer k. Return true if there are two different nodes whose keys add up to k, and false otherwise. Keys in a BST are distinct, so “two different nodes” means two different keys; a single key used twice does not count.
Constraints (LeetCode version): up to 10^4 nodes, keys in [-10^4, 10^4], k in [-10^5, 10^5]. The code below does its sums in long, so it stays correct for any 32-bit keys.
All examples use this tree (in-order: 2 3 4 5 6 7):
5
/ \
3 6
/ \ \
2 4 7
Example 1
k = 9
answer = true // 2 + 7 (also 3 + 6 and 4 + 5)
Example 2
k = 28
answer = false // the largest possible sum is 6 + 7 = 13
Example 3
k = 14
answer = false // 7 + 7 would use the same node twice
Approach 1: Hash set, the Two Sum way
Intuition. Forget that this is a BST and treat it as a bag of numbers. While visiting nodes in any order, ask “have I already seen k - key?”. A hash set answers that in constant time. Adding the current key after the lookup is what stops a node from pairing with itself: when we look up 7 at node 7 for k = 14, the set does not contain 7 yet.
Algorithm.
- Traverse the tree (preorder here) with an empty set
seen. - At each node, if
k - node.valis inseen, returntrue. - Otherwise add
node.valand continue into both children. - Return
falsewhen the traversal finishes.
static boolean withHashSet(TreeNode root, int k) {
return findWithSet(root, k, new HashSet<>());
}
static boolean findWithSet(TreeNode node, int k, Set<Long> seen) {
if (node == null) return false;
if (seen.contains((long) k - node.val)) return true; // long: k - val can overflow int
seen.add((long) node.val); // add after the check so a node never pairs with itself
return findWithSet(node.left, k, seen) || findWithSet(node.right, k, seen);
}
The set stores Long values and the complement is computed as (long) k - node.val, because k - node.val can overflow int when keys are near the limits.
Complexity. Time O(n): every node is visited once, with O(1) average set work. Space O(n) for the set, plus O(h) recursion. This works on any binary tree; it never uses the ordering.
Approach 2: In-order list plus two pointers
Intuition. The in-order traversal of a BST is sorted, and on a sorted array Two Sum has a classic answer: one pointer at the smallest key, one at the largest. If their sum is too small, the smallest key is useless: even its best partner, the largest remaining key, is not enough, so drop it by moving lo right. If the sum is too large, the largest key is useless for the symmetric reason, so move hi left. Every step discards one key that provably has no partner among the remaining keys, so no valid pair is ever skipped.
Algorithm.
- Collect the keys in in-order into a list (sorted).
- Set
lo = 0,hi = size - 1. - While
lo < hi: if the sum equalsk, returntrue; if it is smaller,lo++; otherwisehi--. - Return
false.
static boolean inorderTwoPointers(TreeNode root, int k) {
List<Integer> sorted = new ArrayList<>();
collectInorder(root, sorted);
int lo = 0, hi = sorted.size() - 1;
while (lo < hi) {
long sum = (long) sorted.get(lo) + sorted.get(hi);
if (sum == k) return true;
if (sum < k) lo++; else hi--;
}
return false;
}
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): one traversal and at most n - 1 pointer moves. Space O(n) for the list.
The loop condition lo < hi is what forbids using one node twice. The list is no smaller than the hash set, so this approach is not better yet. It is the stepping stone: the two pointers only ever move inward one step at a time, so they never need random access into the list.
Approach 3: Optimal, two BST iterators
Intuition. A pointer that only moves forward through the in-order sequence is exactly a BST iterator: a stack holding the left spine of the unvisited part, whose top is the next smallest key. Mirror it (push right spines, and after popping go to the left child) and you get an iterator that yields keys from the largest down. Two iterators replace the two indices, and each stack holds at most one root-to-leaf path, so memory drops from O(n) to O(h).
The iterator calls inside the loop are always safe. While lo < hi, the ascending iterator has not yet produced hi, so it still has at least one key to give, and the descending iterator still has lo ahead of it.
Algorithm.
- Create an ascending iterator and a descending iterator over the tree.
lo = asc.next(),hi = desc.next().- While
lo < hi: iflo + hi == k, returntrue; if the sum is smaller,lo = asc.next(); otherwisehi = desc.next(). - Return
false.
static boolean bstIterators(TreeNode root, int k) {
if (root == null) return false;
BSTIterator asc = new BSTIterator(root, false);
BSTIterator desc = new BSTIterator(root, true);
int lo = asc.next(), hi = desc.next();
while (lo < hi) { // distinct keys: lo < hi means two different nodes
long sum = (long) lo + hi;
if (sum == k) return true;
if (sum < k) lo = asc.next(); else hi = desc.next();
}
return false;
}
/** In-order iterator over a BST using a stack of at most h nodes; reverse = true walks from the largest key down. */
static class BSTIterator {
private final Deque<TreeNode> stack = new ArrayDeque<>();
private final boolean reverse;
BSTIterator(TreeNode root, boolean reverse) {
this.reverse = reverse;
pushAll(root);
}
int next() {
TreeNode node = stack.pop();
pushAll(reverse ? node.left : node.right);
return node.val;
}
private void pushAll(TreeNode node) {
while (node != null) {
stack.push(node);
node = reverse ? node.right : node.left;
}
}
}
Dry run with k = 11. Stacks are written bottom to top.
| step | lo | hi | sum | action | asc stack after | desc stack after |
|---|---|---|---|---|---|---|
| start | 2 | 7 | [5, 3] | [5, 6] | ||
| 1 | 2 | 7 | 9 | 9 < 11, advance lo | [5, 4] | [5, 6] |
| 2 | 3 | 7 | 10 | 10 < 11, advance lo | [5] | [5, 6] |
| 3 | 4 | 7 | 11 | found, return true |
When asc popped 3, it pushed the left spine of 3’s right child, which is just 4; that is why 4 appears on its stack in step 1.
Complexity. Time O(n): each iterator pushes and pops every node at most once, so all next() calls together cost O(n), amortised O(1) each. Space O(h) for the two stacks: O(log n) on a balanced tree.
Edge cases and pitfalls
- Same node twice.
k = 14with the single7, ork = 6on[2, 1, 3]with the single3. The hash set checks before inserting; the pointer methods requirelo < hi. - Empty tree and single node. No pair is possible; all methods return
false. The iterator method returns early for an empty tree becausenext()on an empty stack would throw. - Overflow. With keys
2000000000and1999999999, theintsum wraps to-294967297. The test uses exactly thatkand expectsfalse. All three methods add inlong. - Negative keys.
[0, -5, 5]withk = 0istrue. Nothing special is required. - Duplicate keys. A strict BST has none. If a variant allowed them, the iterator method should stop when the two iterators reach the same node, not when the values meet, or it would miss pairs like
3 + 3from two different nodes.
Related problems
- Two Sum is the array version; Approach 1 is its hash map idea and Approach 2 its sorted two-pointer idea.
- BST iterator builds the ascending cursor used in Approach 3.
- Merge 2 BST’s runs two ascending cursors over two different trees.
- Iterative Inorder Traversal of Binary Tree is the traversal the iterator is cut from.
- 3 Sum extends the two-pointer sweep to three elements.