653. Two Sum IV - Input is a BST
Problem Description
You are given the root of a binary search tree (BST) and an integer k
. Your task is to determine whether there exist two different nodes in the BST whose values sum up to exactly k
.
The problem asks you to return true
if you can find any two distinct elements in the tree that add up to the target value k
, and false
if no such pair exists.
For example:
- If the BST contains nodes with values
[5, 3, 6, 2, 4, 7]
andk = 9
, you should returntrue
because3 + 6 = 9
- If the BST contains nodes with values
[5, 3, 6, 2, 4, 7]
andk = 28
, you should returnfalse
because no two values sum to 28
The solution uses a depth-first search (DFS) traversal combined with a hash set. As it visits each node, it:
- Checks if the complement value (
k - current_value
) has already been seen in previous nodes - If the complement exists, it means we found two numbers that sum to
k
, so returntrue
- Otherwise, add the current value to the set of visited values and continue searching
- The search continues recursively through the left and right subtrees until either a valid pair is found or all nodes are processed
The vis
set keeps track of all node values encountered so far, enabling constant-time lookup to check if the required complement exists.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this is a binary search tree problem, a tree is a special type of graph (specifically, an acyclic connected graph). Trees can be traversed using graph traversal algorithms.
Is it a tree?
- Yes: The problem explicitly states we're working with a Binary Search Tree (BST), which is indeed a tree data structure.
DFS
- Yes: Since we're dealing with a tree, DFS (Depth-First Search) is a natural choice for traversal.
Conclusion: The flowchart suggests using DFS for this problem.
This makes perfect sense for our Two Sum IV problem because:
- We need to visit every node in the tree to check all possible pairs
- DFS allows us to traverse the tree systematically while maintaining a set of previously seen values
- As we traverse using DFS, we can check if the complement (
k - current_value
) exists in our set of visited nodes - The recursive nature of DFS fits well with the tree structure, allowing us to explore left and right subtrees naturally
The solution implements this DFS approach by recursively visiting each node, checking for the complement value, and adding the current value to the visited set before continuing the traversal.
Intuition
The key insight for this problem comes from recognizing that we're essentially solving the classic Two Sum problem, but with values stored in a tree structure instead of an array.
In the standard Two Sum problem, we use a hash set to track values we've seen so far. For each number x
, we check if k - x
exists in our set. If it does, we've found our pair. The same principle applies here - we just need to traverse the tree to access all values.
Why does DFS work well here? As we traverse the tree, we're building up our "seen values" set one node at a time. At each node, we ask: "Have I seen the number that would complete this pair?" This is checking if k - current_value
exists in our set. If yes, we're done. If not, we add the current value to our set and keep searching.
The beauty of this approach is that it doesn't matter that we have a BST specifically - we're not using the sorted property. We're simply visiting every node once and checking against our growing collection of seen values. The DFS traversal ensures we systematically explore all nodes while maintaining our set of visited values across the entire traversal.
The recursive nature of DFS matches the recursive structure of the tree perfectly. We process the current node, then recursively check the left subtree, and if that doesn't find a pair, check the right subtree. The or
operation in dfs(root.left) or dfs(root.right)
ensures we stop as soon as we find a valid pair, making the solution efficient.
This approach has a time complexity of O(n)
where we visit each node once, and space complexity of O(n)
for storing the visited values in the set plus the recursion stack.
Learn more about Tree, Depth-First Search, Breadth-First Search, Binary Search Tree, Two Pointers and Binary Tree patterns.
Solution Approach
The solution implements a DFS traversal with a hash set to track visited values. Let's walk through the implementation step by step:
Data Structure Used:
- A hash set
vis
to store all node values we've encountered during traversal
Algorithm Implementation:
-
Initialize the visited set: We create an empty set
vis
that will store all node values we've seen so far. -
Define the DFS function: The recursive
dfs
function takes a node as input and:- Base case: If the current node is
None
, returnFalse
(no pair found in this empty subtree) - Check for complement: Calculate the complement value as
k - root.val
. If this complement exists in ourvis
set, we've found two numbers that sum tok
, so returnTrue
- Add current value: Add the current node's value to the
vis
set usingvis.add(root.val)
- Recursive exploration: Recursively search the left and right subtrees using
dfs(root.left) or dfs(root.right)
. Theor
operator ensures we returnTrue
as soon as either subtree finds a valid pair
- Base case: If the current node is
-
Start the traversal: Call
dfs(root)
to begin the search from the root node.
Key Pattern - Two Sum with DFS: The pattern here combines the classic Two Sum hash set approach with tree traversal. As we visit each node during DFS:
- We check: "Does
k - current_value
exist in our set?" - If yes → we found our pair
- If no → add current value to set and continue searching
Why this works:
- The set gives us
O(1)
lookup time to check if a complement exists - DFS ensures we visit every node exactly once
- The recursive structure naturally handles the tree traversal
- We don't need to worry about using the same element twice because we check for the complement before adding the current value to the set
This approach elegantly solves the problem in a single pass through the tree with O(n)
time and space complexity.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how the DFS with hash set approach works.
Example Tree:
5 / \ 3 6 / \ \ 2 4 7
Target k = 9
Step-by-step execution:
-
Start at root (5):
- Check: Is
9 - 5 = 4
invis
? No (vis is empty) - Add 5 to vis:
vis = {5}
- Recurse to left child (3)
- Check: Is
-
Visit node (3):
- Check: Is
9 - 3 = 6
invis
? No (vis only has {5}) - Add 3 to vis:
vis = {5, 3}
- Recurse to left child (2)
- Check: Is
-
Visit node (2):
- Check: Is
9 - 2 = 7
invis
? No (vis has {5, 3}) - Add 2 to vis:
vis = {5, 3, 2}
- No children, return False
- Backtrack to node 3, recurse to right child (4)
- Check: Is
-
Visit node (4):
- Check: Is
9 - 4 = 5
invis
? Yes! (5 is in vis) - Return True immediately
- Check: Is
The function returns true
because we found that 4 + 5 = 9. The algorithm discovered this pair by finding that when we visited node 4, its complement (5) was already in our visited set.
Key insight: Notice how we found the pair without visiting all nodes. Once we found a valid pair (4 and 5), the recursion stopped due to the or
operator short-circuiting. If no pair existed, we would continue until all nodes were visited.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8class Solution:
9 def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
10 """
11 Finds if there exist two elements in the BST such that their sum equals k.
12
13 Args:
14 root: Root node of the binary search tree
15 k: Target sum to find
16
17 Returns:
18 True if two elements sum to k, False otherwise
19 """
20
21 def dfs(node: Optional[TreeNode]) -> bool:
22 """
23 Performs depth-first search to find two nodes that sum to k.
24
25 Args:
26 node: Current node being processed
27
28 Returns:
29 True if pair found, False otherwise
30 """
31 # Base case: reached null node
32 if node is None:
33 return False
34
35 # Check if complement of current value exists in visited set
36 complement = k - node.val
37 if complement in visited_values:
38 return True
39
40 # Add current node value to visited set
41 visited_values.add(node.val)
42
43 # Recursively search left and right subtrees
44 return dfs(node.left) or dfs(node.right)
45
46 # Initialize set to store visited node values
47 visited_values = set()
48
49 # Start DFS from root
50 return dfs(root)
51
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 // Set to store visited node values for O(1) lookup
18 private Set<Integer> visitedValues = new HashSet<>();
19
20 // Target sum we're looking for
21 private int targetSum;
22
23 /**
24 * Finds if there exist two elements in the BST that add up to the target sum.
25 *
26 * @param root The root of the binary search tree
27 * @param k The target sum to find
28 * @return true if two elements sum to k, false otherwise
29 */
30 public boolean findTarget(TreeNode root, int k) {
31 this.targetSum = k;
32 return depthFirstSearch(root);
33 }
34
35 /**
36 * Performs depth-first search to find two numbers that sum to target.
37 * Uses the two-sum approach: for each node value x, check if (target - x) exists.
38 *
39 * @param node Current node being processed
40 * @return true if a valid pair is found, false otherwise
41 */
42 private boolean depthFirstSearch(TreeNode node) {
43 // Base case: null node
44 if (node == null) {
45 return false;
46 }
47
48 // Check if complement (targetSum - current value) exists in visited set
49 if (visitedValues.contains(targetSum - node.val)) {
50 return true;
51 }
52
53 // Add current node value to visited set
54 visitedValues.add(node.val);
55
56 // Recursively search left and right subtrees
57 return depthFirstSearch(node.left) || depthFirstSearch(node.right);
58 }
59}
60
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Find if there exist two elements in a BST that sum up to target k
16 * @param root: The root of the binary search tree
17 * @param k: The target sum to find
18 * @return: True if two elements sum to k, false otherwise
19 */
20 bool findTarget(TreeNode* root, int k) {
21 // Hash set to store visited node values
22 unordered_set<int> visitedValues;
23
24 // Lambda function for depth-first search traversal
25 function<bool(TreeNode*)> dfs = [&](TreeNode* currentNode) -> bool {
26 // Base case: if node is null, return false
27 if (!currentNode) {
28 return false;
29 }
30
31 // Check if complement (k - current value) exists in visited set
32 // If it exists, we found two numbers that sum to k
33 if (visitedValues.count(k - currentNode->val)) {
34 return true;
35 }
36
37 // Add current node's value to visited set
38 visitedValues.insert(currentNode->val);
39
40 // Recursively search left and right subtrees
41 // Return true if either subtree contains the pair
42 return dfs(currentNode->left) || dfs(currentNode->right);
43 };
44
45 // Start DFS from root
46 return dfs(root);
47 }
48};
49
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15// Set to store visited node values
16let visitedValues: Set<number>;
17
18/**
19 * Performs depth-first search to find if two numbers in the tree sum to target
20 * @param node - Current node being processed
21 * @param target - The target sum we're looking for
22 * @returns true if a pair is found, false otherwise
23 */
24function depthFirstSearch(node: TreeNode | null, target: number): boolean {
25 // Base case: if node is null, return false
26 if (!node) {
27 return false;
28 }
29
30 // Check if the complement (target - current value) exists in our set
31 const complement: number = target - node.val;
32 if (visitedValues.has(complement)) {
33 return true;
34 }
35
36 // Add current node's value to the set of visited values
37 visitedValues.add(node.val);
38
39 // Recursively search left and right subtrees
40 // Return true if either subtree finds a valid pair
41 return depthFirstSearch(node.left, target) || depthFirstSearch(node.right, target);
42}
43
44/**
45 * Finds if there exist two elements in the BST that sum to k
46 * @param root - Root of the binary search tree
47 * @param k - Target sum to find
48 * @returns true if two elements sum to k, false otherwise
49 */
50function findTarget(root: TreeNode | null, k: number): boolean {
51 // Initialize the set to track visited values
52 visitedValues = new Set<number>();
53
54 // Start the depth-first search from root
55 return depthFirstSearch(root, k);
56}
57
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree.
The algorithm performs a depth-first search (DFS) traversal of the binary tree. Each node is visited exactly once during the traversal. For each node visited, the algorithm performs:
- A constant time check to see if
k - root.val
exists in the hash set (O(1)
average case) - A constant time insertion of
root.val
into the hash set (O(1)
average case)
Since we visit each of the n
nodes once and perform O(1)
operations at each node, the overall time complexity is O(n)
.
Space Complexity: O(n)
where n
is the number of nodes in the binary tree.
The space complexity consists of two components:
- The hash set
vis
that stores the values of visited nodes. In the worst case, we might store alln
node values in the set before finding the target sum or determining it doesn't exist, requiringO(n)
space. - The recursive call stack for DFS. In the worst case (a skewed tree), the recursion depth can be
O(n)
. In the best case (a balanced tree), the recursion depth would beO(log n)
.
Taking the worst case for both components, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Using the Same Element Twice
The Problem: A common mistake is forgetting to handle the case where k
is exactly double a node's value. For instance, if a node has value 5
and k = 10
, the algorithm might incorrectly return true
by using the same node twice (5 + 5 = 10).
Why It Happens: After adding a value to the visited set, if we encounter the same value again in the tree, the complement check might match with itself.
The Solution: The current implementation actually handles this correctly! By checking for the complement before adding the current value to the set, we ensure we never match a node with itself. The order matters:
# CORRECT: Check first, then add if complement in visited_values: # Check for complement return True visited_values.add(node.val) # Then add current value # WRONG: Add first, then check (could match with itself) visited_values.add(node.val) # If we add first... if complement in visited_values: # ...we might match the same element return True
Pitfall 2: Not Utilizing BST Properties
The Problem: The current solution treats the tree as a generic binary tree and doesn't leverage the BST property (left subtree < root < right subtree). This results in O(n) space complexity for the hash set.
Why It Matters: For very large trees, storing all values in memory could be problematic.
Alternative Solution: Use two pointers on an in-order traversal:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
# Convert BST to sorted array via in-order traversal
def inorder(node):
if not node:
return []
return inorder(node.left) + [node.val] + inorder(node.right)
# Use two pointers on sorted array
arr = inorder(root)
left, right = 0, len(arr) - 1
while left < right:
curr_sum = arr[left] + arr[right]
if curr_sum == k:
return True
elif curr_sum < k:
left += 1
else:
right -= 1
return False
Pitfall 3: Integer Overflow in Other Languages
The Problem: In languages like C++ or Java, calculating k - node.val
could cause integer overflow if k
is at the minimum integer value and node.val
is positive.
Why It Happens: When k = INT_MIN
and we subtract a positive value, the result could overflow.
The Solution: In Python, this isn't an issue due to arbitrary precision integers. In other languages, use careful bounds checking or cast to a larger data type:
// Java example - use long to avoid overflow long complement = (long)k - node.val;
Pitfall 4: Modifying the Visited Set During Recursion
The Problem: If you accidentally pass the visited set by reference and modify it incorrectly in recursive calls, you might lose track of previously seen values.
Why It Happens: Confusion about variable scope in nested functions.
The Solution: The current implementation correctly uses a closure where the visited_values
set is defined in the outer function scope and shared across all recursive calls. This ensures all nodes' values persist in the set throughout the entire traversal.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!