Before attempting this problem, you should be comfortable with:
Binary Search Tree (BST) properties - Understanding that inorder traversal yields sorted values and how to search in O(log n) time
Tree traversal (DFS) - Recursively visiting all nodes to collect values or search for complements
Hash Sets - Storing values for O(1) lookup when checking if a complement exists
Two Pointers technique - Using pointers on sorted lists to efficiently find pairs that sum to a target
1. Brute Force
Intuition
The simplest approach is to collect all values from both trees and check every possible pair. If any pair sums to the target, we have found our answer.
Algorithm
Traverse the first tree and store all node values in a list.
Traverse the second tree and store all node values in another list.
For each value a in the first list and each value b in the second list, check if a + b == target.
Return true if a valid pair is found, false otherwise.
class Solution {privatefundfs(currNode: TreeNode?, nodeList: MutableList<Int>){if(currNode ==null){return}
nodeList.add(currNode.`val`)dfs(currNode.left, nodeList)dfs(currNode.right, nodeList)}funtwoSumBSTs(root1: TreeNode?, root2: TreeNode?, target: Int): Boolean {val nodeList1 = mutableListOf<Int>()val nodeList2 = mutableListOf<Int>()dfs(root1, nodeList1)dfs(root2, nodeList2)for(a in nodeList1){for(b in nodeList2){if(a + b == target){returntrue}}}returnfalse}}
classSolution{functwoSumBSTs(_ root1:TreeNode?,_ root2:TreeNode?,_ target:Int)->Bool{funcdfs(_ currNode:TreeNode?,_ nodeList:inout[Int]){guardlet currNode = currNode else{return}
nodeList.append(currNode.val)dfs(currNode.left,&nodeList)dfs(currNode.right,&nodeList)}var nodeList1 =[Int](), nodeList2 =[Int]()dfs(root1,&nodeList1)dfs(root2,&nodeList2)for a in nodeList1 {for b in nodeList2 {if a + b == target {returntrue}}}returnfalse}}
Time & Space Complexity
Time complexity: O(m⋅n)
Space complexity: O(m+n)
Where m and n are the number of nodes in the two trees.
2. Binary Search
Intuition
Since the second tree is a BST, we can leverage its sorted structure. For each node in the first tree, we compute the complement (target - node.val) and use binary search to check if that complement exists in the second tree.
Algorithm
Traverse the first BST using dfs.
For each node with value v, compute complement = target - v.
Perform binary search in the second BST to find the complement:
If current value equals complement, return true.
If current value is greater, search left.
Otherwise, search right.
If no pair is found after checking all nodes, return false.
Where m and n are the number of nodes in the two trees.
3. Hash Set
Intuition
We can trade space for time by storing all values from one tree in a hash set. Then for each value in the other tree, we check if the complement exists in the set in O(1) time.
Algorithm
Traverse the first tree and add all values to a hash set.
Traverse the second tree and add all values to another hash set.
For each value in the first set, check if target - value exists in the second set.
Where m and n are the number of nodes in the two trees.
4. Two Pointers
Intuition
An inorder traversal of a BST produces a sorted list. If we have sorted lists from both trees, we can use the classic two-pointer technique: one pointer starts at the beginning of the first list (smallest), and another starts at the end of the second list (largest). We adjust pointers based on whether the current sum is too small or too large.
Algorithm
Perform inorder traversal on both trees to get two sorted lists.
Initialize pointer1 = 0 (start of first list) and pointer2 = len(list2) - 1 (end of second list).
While both pointers are valid:
If sum equals target, return true.
If sum is less than target, increment pointer1.
If sum is greater than target, decrement pointer2.
Where m and n are the number of nodes in the two trees.
5. Morris Traversal
Intuition
The two-pointer approach requires O(m + n) space to store the sorted lists. Morris traversal lets us iterate through a BST in sorted order using O(1) extra space by temporarily modifying tree pointers. We use forward Morris traversal on one tree and reverse Morris traversal on the other to simulate the two-pointer technique without extra space.
Algorithm
Create a forward Morris iterator for the first tree (yields values in ascending order).
Create a reverse Morris iterator for the second tree (yields values in descending order).
Get the first value from each iterator.
While both values are valid:
If sum equals target, return true.
If sum is less than target, advance the forward iterator.
If sum is greater than target, advance the reverse iterator.
publicclassSolution{privateclassMorrisIterator{privateTreeNode current;privateTreeNode pre;publicMorrisIterator(TreeNode root){
current = root;
pre =null;}publicboolHasNext(){return current !=null;}publicint?Next(){int? val =null;while(current !=null){if(current.left ==null){
val = current.val;
current = current.right;break;}else{
pre = current.left;while(pre.right !=null&& pre.right != current){
pre = pre.right;}if(pre.right ==null){
pre.right = current;
current = current.left;}else{
pre.right =null;
val = current.val;
current = current.right;break;}}}return val;}}privateclassReversedMorrisIterator{privateTreeNode current;privateTreeNode pre;publicReversedMorrisIterator(TreeNode root){
current = root;
pre =null;}publicboolHasNext(){return current !=null;}publicint?Next(){int? val =null;while(current !=null){if(current.right ==null){
val = current.val;
current = current.left;break;}else{
pre = current.right;while(pre.left !=null&& pre.left != current){
pre = pre.left;}if(pre.left ==null){
pre.left = current;
current = current.right;}else{
pre.left =null;
val = current.val;
current = current.left;break;}}}return val;}}publicboolTwoSumBSTs(TreeNode root1,TreeNode root2,int target){var iterator1 =newMorrisIterator(root1);var iterator2 =newReversedMorrisIterator(root2);int? value1 = iterator1.Next();int? value2 = iterator2.Next();while(value1 !=null&& value2 !=null){if(value1 + value2 == target){returntrue;}elseif(value1 + value2 < target){
value1 = iterator1.HasNext()? iterator1.Next():null;}else{
value2 = iterator2.HasNext()? iterator2.Next():null;}}returnfalse;}}
functwoSumBSTs(root1 *TreeNode, root2 *TreeNode, target int)bool{
morrisChan :=func(root *TreeNode)<-chanint{
ch :=make(chanint)gofunc(){deferclose(ch)
current := root
for current !=nil{if current.Left ==nil{
ch <- current.Val
current = current.Right
}else{
pre := current.Left
for pre.Right !=nil&& pre.Right != current {
pre = pre.Right
}if pre.Right ==nil{
pre.Right = current
current = current.Left
}else{
pre.Right =nil
ch <- current.Val
current = current.Right
}}}}()return ch
}
reversedMorrisChan :=func(root *TreeNode)<-chanint{
ch :=make(chanint)gofunc(){deferclose(ch)
current := root
for current !=nil{if current.Right ==nil{
ch <- current.Val
current = current.Left
}else{
pre := current.Right
for pre.Left !=nil&& pre.Left != current {
pre = pre.Left
}if pre.Left ==nil{
pre.Left = current
current = current.Right
}else{
pre.Left =nil
ch <- current.Val
current = current.Left
}}}}()return ch
}
ch1 :=morrisChan(root1)
ch2 :=reversedMorrisChan(root2)
value1, ok1 :=<-ch1
value2, ok2 :=<-ch2
for ok1 && ok2 {if value1+value2 == target {returntrue}elseif value1+value2 < target {
value1, ok1 =<-ch1
}else{
value2, ok2 =<-ch2
}}returnfalse}
class Solution {privateclassMorrisIterator(root: TreeNode?): Iterator<Int>{privatevar current: TreeNode?= root
privatevar pre: TreeNode?=nullprivatevar nextVal: Int?=nullinit{advance()}privatefunadvance(){
nextVal =nullwhile(current !=null){if(current!!.left ==null){
nextVal = current!!.`val`
current = current!!.right
break}else{
pre = current!!.left
while(pre!!.right !=null&& pre!!.right != current){
pre = pre!!.right
}if(pre!!.right ==null){
pre!!.right = current
current = current!!.left
}else{
pre!!.right =null
nextVal = current!!.`val`
current = current!!.right
break}}}}overridefunhasNext(): Boolean = nextVal !=nulloverridefunnext(): Int {val result = nextVal!!advance()return result
}}privateclassReversedMorrisIterator(root: TreeNode?): Iterator<Int>{privatevar current: TreeNode?= root
privatevar pre: TreeNode?=nullprivatevar nextVal: Int?=nullinit{advance()}privatefunadvance(){
nextVal =nullwhile(current !=null){if(current!!.right ==null){
nextVal = current!!.`val`
current = current!!.left
break}else{
pre = current!!.right
while(pre!!.left !=null&& pre!!.left != current){
pre = pre!!.left
}if(pre!!.left ==null){
pre!!.left = current
current = current!!.right
}else{
pre!!.left =null
nextVal = current!!.`val`
current = current!!.left
break}}}}overridefunhasNext(): Boolean = nextVal !=nulloverridefunnext(): Int {val result = nextVal!!advance()return result
}}funtwoSumBSTs(root1: TreeNode?, root2: TreeNode?, target: Int): Boolean {val iterator1 =MorrisIterator(root1)val iterator2 =ReversedMorrisIterator(root2)var value1: Int?=if(iterator1.hasNext()) iterator1.next()elsenullvar value2: Int?=if(iterator2.hasNext()) iterator2.next()elsenullwhile(value1 !=null&& value2 !=null){when{
value1 + value2 == target ->returntrue
value1 + value2 < target -> value1 =if(iterator1.hasNext()) iterator1.next()elsenullelse-> value2 =if(iterator2.hasNext()) iterator2.next()elsenull}}returnfalse}}
classSolution{functwoSumBSTs(_ root1:TreeNode?,_ root2:TreeNode?,_ target:Int)->Bool{classMorrisIterator:IteratorProtocol{privatevar current:TreeNode?privatevar pre:TreeNode?init(_ root:TreeNode?){
current = root
pre =nil}funcnext()->Int?{var val:Int?=nilwhile current !=nil{if current!.left==nil{
val = current!.val
current = current!.rightbreak}else{
pre = current!.leftwhile pre!.right!=nil&& pre!.right!== current {
pre = pre!.right}if pre!.right==nil{
pre!.right= current
current = current!.left}else{
pre!.right=nil
val = current!.val
current = current!.rightbreak}}}return val
}}classReversedMorrisIterator:IteratorProtocol{privatevar current:TreeNode?privatevar pre:TreeNode?init(_ root:TreeNode?){
current = root
pre =nil}funcnext()->Int?{var val:Int?=nilwhile current !=nil{if current!.right==nil{
val = current!.val
current = current!.leftbreak}else{
pre = current!.rightwhile pre!.left!=nil&& pre!.left!== current {
pre = pre!.left}if pre!.left==nil{
pre!.left= current
current = current!.right}else{
pre!.left=nil
val = current!.val
current = current!.leftbreak}}}return val
}}var iterator1 =MorrisIterator(root1)var iterator2 =ReversedMorrisIterator(root2)var value1 = iterator1.next()var value2 = iterator2.next()while value1 !=nil&& value2 !=nil{let sum = value1!+ value2!if sum == target {returntrue}elseif sum < target {
value1 = iterator1.next()}else{
value2 = iterator2.next()}}returnfalse}}
Time & Space Complexity
Time complexity: O(m+n)
Space complexity: O(1)
Where m and n are the number of nodes in the two trees.
Common Pitfalls
Treating the Trees as Regular Binary Trees
A common mistake is ignoring the BST property and treating both trees as generic binary trees. This leads to inefficient solutions that don't leverage the sorted structure for binary search or two-pointer approaches. Always consider whether the BST ordering can help optimize your search.
Computing the Complement Incorrectly
When searching for a pair that sums to the target, some forget to compute target - value correctly or accidentally search for the wrong value. For example, if target = 9 and the current node has value 4, you need to search for 5 in the other tree, not 9 - 4 = 5 with any other formula.
Modifying Tree Structure with Morris Traversal
Morris traversal temporarily modifies tree pointers to achieve O(1) space. A pitfall is forgetting to restore the original tree structure after traversal or exiting early without cleanup. This can corrupt the tree for subsequent operations or cause infinite loops.