Given the root of a binary search tree (BST) and an integer target
, split the BST into two subtrees:
target
.target
.Both resulting subtrees should maintain the properties of a BST. The function should return the roots of these two subtrees as a list or array of two elements. You must not reuse or duplicate any nodes — each node from the original tree should appear in exactly one of the resulting subtrees.
There is exactly one valid way to split the BST for a given target
, and you do not need to balance the trees after splitting.
To solve this problem, let's first consider the unique properties of a BST:
target
value. A naïve approach might be to traverse the tree and insert each node into one of two new trees, but this would require creating new nodes and would not be efficient or in line with the problem's constraints.
target
, it belongs in the "small" tree, and we may need to split its right subtree.target
, it belongs in the "large" tree, and we may need to split its left subtree.
We'll use a recursive function that, given a node and the target
, returns two roots: the root of the "small" BST (all values ≤ target
) and the root of the "large" BST (all values > target
).
null
, return [null, null]
— both subtrees are empty.
node.val ≤ target
:
target
.[current node, large subtree from right]
.node.val > target
:
target
.[small subtree from left, current node]
.This approach is efficient because each node is visited only once, and we only modify pointers.
Suppose the BST is:
4 / \ 2 6 / \ / \ 1 3 5 7
Let target = 2
. We want to split the tree into:
Brute-force approach: If we were to traverse the tree and insert each node into a new BST, the time complexity would be O(N log N) (for N insertions), and space complexity would be O(N) due to new nodes.
Optimized recursive approach: Each node is visited exactly once, so time complexity is O(N), where N is the number of nodes. Space complexity is O(H), where H is the height of the tree, due to the recursion stack.
The key insight for splitting a BST is to use its structure and recursion: at each node, decide which subtree it belongs to based on the target
, and recursively split only the necessary subtrees. This ensures both efficiency and correctness, with no need to create new nodes or extra data structures. The elegance of this solution lies in its simplicity and the direct use of BST properties.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def splitBST(self, root, target):
if not root:
return [None, None]
if root.val <= target:
small, large = self.splitBST(root.right, target)
root.right = small
return [root, large]
else:
small, large = self.splitBST(root.left, target)
root.left = large
return [small, root]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<TreeNode*> splitBST(TreeNode* root, int target) {
if (!root) return {nullptr, nullptr};
if (root->val <= target) {
auto splitted = splitBST(root->right, target);
root->right = splitted[0];
return {root, splitted[1]};
} else {
auto splitted = splitBST(root->left, target);
root->left = splitted[1];
return {splitted[0], root};
}
}
};
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode[] splitBST(TreeNode root, int target) {
if (root == null) return new TreeNode[]{null, null};
if (root.val <= target) {
TreeNode[] splitted = splitBST(root.right, target);
root.right = splitted[0];
return new TreeNode[]{root, splitted[1]};
} else {
TreeNode[] splitted = splitBST(root.left, target);
root.left = splitted[1];
return new TreeNode[]{splitted[0], root};
}
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} target
* @return {[TreeNode, TreeNode]}
*/
var splitBST = function(root, target) {
if (!root) return [null, null];
if (root.val <= target) {
let [small, large] = splitBST(root.right, target);
root.right = small;
return [root, large];
} else {
let [small, large] = splitBST(root.left, target);
root.left = large;
return [small, root];
}
};