Given the root of a binary search tree (BST) and an integer target
, split the tree into two subtrees:
Return the roots of the two subtrees after the split.
Example 1:
Input: root = [4,2,6,1,3,5,7], target = 2 Output: [[2,1],[4,3,6,null,null,5,7]]
Example 2:
Input: root = [1,null,3,0,2,4,null], target = 4 Output: [[1,null,3,0,2],[4]]
Constraints:
[1, 500]
.0 <= Node.val <= 1000
target >= 0
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to splitting a Binary Search Tree (BST) at a certain value involves examining all possible ways to separate the tree. We consider every node as a potential splitting point and recursively explore all resulting subtrees. Ultimately, we pick the split that adheres to the BST properties and the splitting value.
Here's how the algorithm would work step-by-step:
def split_bst_brute_force(root, value):
possible_splits = []
def is_bst(root_node, min_value=float('-inf'), max_value=float('inf')):
if not root_node:
return True
if not (min_value < root_node.val < max_value):
return False
return (is_bst(root_node.left, min_value, root_node.val)
and is_bst(root_node.right, root_node.val, max_value))
def split_tree(root_node, current_value):
less_equal_tree = None
greater_tree = None
if not root_node:
return None, None
# Create a new tree containing elements <= value
def build_less_equal_tree(node):
if not node:
return None
new_node = TreeNode(node.val)
new_node.left = build_less_equal_tree(node.left)
new_node.right = build_less_equal_tree(node.right)
return new_node
# Create a new tree containing elements > value
def build_greater_tree(node):
if not node:
return None
new_node = TreeNode(node.val)
new_node.left = build_greater_tree(node.left)
new_node.right = build_greater_tree(node.right)
return new_node
less_equal_root = build_less_equal_tree(root)
greater_root = build_greater_tree(root)
less_equal_nodes = []
greater_nodes = []
def inorder_traversal(node, result_list):
if node:
inorder_traversal(node.left, result_list)
result_list.append(node.val)
inorder_traversal(node.right, result_list)
inorder_traversal(less_equal_root, less_equal_nodes)
inorder_traversal(greater_root, greater_nodes)
less_equal_tree_valid = True
greater_tree_valid = True
for node_value in less_equal_nodes:
if node_value > current_value:
less_equal_tree_valid = False
break
for node_value in greater_nodes:
if node_value <= current_value:
greater_tree_valid = False
break
less_equal_tree = None
greater_tree = None
def create_tree(node_values):
if not node_values:
return None
root_node = TreeNode(node_values[0])
for value in node_values[1:]:
current = root_node
while True:
if value < current.val:
if current.left is None:
current.left = TreeNode(value)
break
else:
current = current.left
else:
if current.right is None:
current.right = TreeNode(value)
break
else:
current = current.right
return root_node
less_equal_tree = create_tree(less_equal_nodes)
greater_tree = create_tree(greater_nodes)
return less_equal_tree, greater_tree
# Need to examine every single node in the tree
def traverse(root_node):
if not root_node:
return
# Create the split when examining the current node
less_equal, greater = split_tree(root, root_node.val)
if is_bst(less_equal) and is_bst(greater):
# Store the results if they are valid BST
possible_splits.append((less_equal, greater))
traverse(root_node.left)
traverse(root_node.right)
traverse(root)
best_split = None
min_nodes = float('inf')
# Find the best split of all the possible splits.
for less_equal_tree, greater_tree in possible_splits:
less_equal_node_count = count_nodes(less_equal_tree)
# We are trying to minimize the number of nodes
if less_equal_node_count < min_nodes:
min_nodes = less_equal_node_count
best_split = (less_equal_tree, greater_tree)
return best_split if best_split else (None, None)
def count_nodes(root_node):
if not root_node:
return 0
return 1 + count_nodes(root_node.left) + count_nodes(root_node.right)
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
The problem asks us to divide a Binary Search Tree (BST) into two BSTs, one containing nodes with values less than or equal to a given value, and the other containing the remaining nodes. The clever idea is to recursively traverse the tree, making use of the BST property at each node to efficiently split it.
Here's how the algorithm would work step-by-step:
def split_bst(root, target_value):
if not root:
return None, None
if root.val <= target_value:
# Keep root in the 'less than or equal' tree
less_than_or_equal_to_tree, greater_than_tree = split_bst(root.right, target_value)
root.right = less_than_or_equal_to_tree
return root, greater_than_tree
else:
# Keep root in the 'greater than' tree
less_than_or_equal_to_tree, greater_than_tree = split_bst(root.left, target_value)
root.left = greater_than_tree
# Attach the right sub tree found during splitting to the left of root.
return less_than_or_equal_to_tree, root
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def construct_bst():
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(7)
return root
if __name__ == '__main__':
root = construct_bst()
target_value = 3
less_than_or_equal_to_tree, greater_than_tree = split_bst(root, target_value)
#Can't be tested because it's a visual tree, no assertion
print("BST Splitted")
Case | How to Handle |
---|---|
Null root or empty tree | Return [None, None] immediately as there's nothing to split. |
Value is smaller than the smallest element in BST | Return [None, root] as the left subtree will be empty. |
Value is larger than the largest element in BST | Return [root, None] as the right subtree will be empty. |
Tree with only one node | If the node's value is less than or equal to 'val', return [root, None], otherwise return [None, root]. |
Tree with highly skewed structure (e.g., linked list) | The recursive calls will still traverse the tree, but performance might degrade to O(n) where n is the number of nodes. |
Value exists in the tree; multiple nodes equal to 'val' | Split based on the first encountered node equal to 'val' during the traversal; behavior will be consistent. |
Integer overflow during value comparisons if values are close to MAX_INT or MIN_INT | Ensure value is cast to a wider type or use libraries that handle the overflow for comparing to avoid erroneous comparisons. |
Deeply unbalanced tree exceeding recursion depth limit | Consider converting to iterative approach to avoid stack overflow with extremely large or unbalanced trees. |