1932. Merge BSTs to Create Single BST
Problem Description
You are given n binary search tree (BST) root nodes for n separate BSTs stored in an array called trees
(0-indexed). Each BST in trees
has at most 3 nodes, and no two roots have the same value. In one operation, you can:
Return the root of the resulting BST if it is possible to form a valid BST after performing n - 1 operations, or null
if it is impossible to create a valid BST.
A BST (binary search tree) is a binary tree where each node satisfies the following property:
- The value of any node to the left is lesser than the value of the current node.
- The value of any node to the right is greater than the value of the current node.
A leaf is a node that has no children.
Example
Suppose we have the following BSTs:
2 3 5 / \ \ / \ 1 3 4 3 6
One possible sequence of operations to create a valid BST is:
- Merge the first and second BSTs:
2 / \ 1 3 \ 4
- Merge the resulting BST with the third BST:
5 / \ 2 6 / \ 1 3 \ 4
Since we were able to create a valid BST, the function returns the root node of the final BST, in this case, the node with value 5
.
Solution Approach
The solution uses a depth-first search approach to build the final BST. Here are the steps of the algorithm:
- Create two hash tables:
valToNode
to store each root node indexed by its value andcount
to store the frequencies of each value. - Iterate through the input
trees
, updating the hash tables. - For each tree in
trees
, check if the count of the tree's root value is 1. If it is, try to build a BST using a helper functionisValidBST
. If the resulting BST is valid andvalToNode
has at most one remaining entry, return the tree's root. - If no valid BST can be created, return
null
.
The helper function isValidBST
performs a depth-first search to build a valid BST. It checks if the current tree node's value is within the specified range (minNode and maxNode) and whether the current node has children. If the current node has no children, it updates the node with the next node from valToNode
and removes the entry from the hash table. The function continues checking the left and right subtrees and returns true if a valid BST is formed without any remaining entries in valToNode
.
C++ Solution
cpp
#include <unordered_map>
#include <vector>
using namespace std;
// Definition of TreeNode
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right)
: val(x), left(left), right(right) {}
};
class Solution {
public:
TreeNode* canMerge(vector<TreeNode*>& trees) {
unordered_map<int, TreeNode*> valToNode; // {val: node}
unordered_map<int, int> count; // {val: freq}
for (TreeNode* tree : trees) {
valToNode[tree->val] = tree;
++count[tree->val];
if (tree->left)
++count[tree->left->val];
if (tree->right)
++count[tree->right->val];
}
for (TreeNode* tree : trees)
if (count[tree->val] == 1) {
if (isValidBST(tree, nullptr, nullptr, valToNode) &&
valToNode.size() <= 1)
return tree;
return nullptr;
}
return nullptr;
}
private:
bool isValidBST(TreeNode* tree, TreeNode* minNode, TreeNode* maxNode,
unordered_map<int, TreeNode*>& valToNode) {
if (tree == nullptr)
return true;
if (minNode && tree->val <= minNode->val)
return false;
if (maxNode && tree->val >= maxNode->val)
return false;
if (!tree->left && !tree->right && valToNode.count(tree->val)) {
const int val = tree->val;
tree->left = valToNode[val]->left;
tree->right = valToNode[val]->right;
valToNode.erase(val);
}
return isValidBST(tree->left, minNode, tree, valToNode) &&
isValidBST(tree->right, tree, maxNode, valToNode);
}
};
In this C++ solution, two unordered maps are used to store the root nodes and their frequencies. The main function canMerge
and the helper function isValidBST
operate on these hash tables and TreeNode objects to build and validate the final BST.
In summary, this solution combines smaller BSTs into a final valid BST by performing a depth-first search and making use of hash tables to keep track of root nodes and their frequencies.### Python Solution
python
from collections import defaultdict
from typing import Optional
# Definition of TreeNode
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def canMerge(self, trees: list[TreeNode]) -> Optional[TreeNode]:
valToNode = {t.val: t for t in trees} # {val: node}
count = defaultdict(int) # {val: freq}
for tree in trees:
count[tree.val] += 1
if tree.left:
count[tree.left.val] += 1
if tree.right:
count[tree.right.val] += 1
for tree in trees:
if count[tree.val] == 1:
if self.isValidBST(tree, None, None, valToNode) and len(valToNode) <= 1:
return tree
return None
return None
def isValidBST(self, tree: TreeNode, minNode: TreeNode, maxNode: TreeNode, valToNode: dict) -> bool:
if not tree:
return True
if minNode and tree.val <= minNode.val:
return False
if maxNode and tree.val >= maxNode.val:
return False
if not tree.left and not tree.right and tree.val in valToNode:
val = tree.val
tree.left = valToNode[val].left
tree.right = valToNode[val].right
del valToNode[val]
return self.isValidBST(tree.left, minNode, tree, valToNode) and \
self.isValidBST(tree.right, tree, maxNode, valToNode)
The Python solution is very similar to the C++ solution, utilizing a dictionary to store the root nodes and their frequencies, and a defaultdict
for the count. The main function canMerge
and the helper function isValidBST
work together to create and validate the final BST.
Java Solution
java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// Definition of TreeNode
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public class Solution {
public TreeNode canMerge(List<TreeNode> trees) {
Map<Integer, TreeNode> valToNode = new HashMap<>(); // {val: node}
Map<Integer, Integer> count = new HashMap<>(); // {val: freq}
for (TreeNode tree : trees) {
valToNode.put(tree.val, tree);
count.put(tree.val, count.getOrDefault(tree.val, 0) + 1);
if (tree.left != null)
count.put(tree.left.val, count.getOrDefault(tree.left.val, 0) + 1);
if (tree.right != null)
count.put(tree.right.val, count.getOrDefault(tree.right.val, 0) + 1);
}
for (TreeNode tree : trees)
if (count.get(tree.val) == 1) {
if (isValidBST(tree, null, null, valToNode) && valToNode.size() <= 1)
return tree;
return null;
}
return null;
}
private boolean isValidBST(TreeNode tree, TreeNode minNode, TreeNode maxNode, Map<Integer, TreeNode> valToNode) {
if (tree == null)
return true;
if (minNode != null && tree.val <= minNode.val)
return false;
if (maxNode != null && tree.val >= maxNode.val)
return false;
if (tree.left == null && tree.right == null && valToNode.containsKey(tree.val)) {
int val = tree.val;
tree.left = valToNode.get(val).left;
tree.right = valToNode.get(val).right;
valToNode.remove(val);
}
return isValidBST(tree.left, minNode, tree, valToNode) &&
isValidBST(tree.right, tree, maxNode, valToNode);
}
}
The Java solution is also similar to both the C++ and Python solutions, utilizing a HashMap
to store the root nodes and their frequencies. The main function canMerge
and the helper function isValidBST
work on these hash tables and TreeNode
objects to build and validate the resulting BST.
Now, we have provided solutions in three languages, Python, Java, and C++, using the same depth-first search approach and making use of hash tables to keep track of root nodes and their frequencies.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorWhich two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!