You are given the root
of a binary search tree (BST). Your task is to return a balanced binary search tree that contains the exact same node values as the original tree. It's important to note that if there exist multiple possible balanced BSTs, you are free to return any one of them.
A binary search tree is considered balanced if, for every node within the tree, the difference in depth between its left and right subtrees never exceeds 1. This ensures that the tree remains relatively evenly distributed, preventing it from becoming overly skewed.
Let's consider a couple of examples:
Example 1:
Imagine the input tree looks like this: [1,null,2,null,3,null,4,null,null]
This represents an unbalanced tree where each node is a right child of its parent. A valid output would be: [2,1,3,null,null,null,4]
Example 2:
Now, consider a more balanced input: [2,1,3]
In this case, the tree is already balanced, so the function should return the same tree: [2,1,3]
Can you provide an algorithm and corresponding code to efficiently balance a given binary search tree? Think about approaches that minimize space usage and ensure that the resulting tree adheres to the balanced BST criteria.
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 method for balancing a binary search tree involves exploring every possible tree structure. We essentially try out every arrangement of the tree's elements. We then check if each arrangement is both a valid binary search tree and if it's balanced.
Here's how the algorithm would work step-by-step:
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def generate_trees(values):
if not values:
return [None]
trees = []
for i in range(len(values)):
root_value = values[i]
left_values = values[:i]
right_values = values[i+1:]
left_subtrees = generate_trees(left_values)
right_subtrees = generate_trees(right_values)
for left_subtree in left_subtrees:
for right_subtree in right_subtrees:
root = Node(root_value)
root.left = left_subtree
root.right = right_subtree
trees.append(root)
return trees
def is_bst(root):
def is_bst_helper(node, min_value, max_value):
if not node:
return True
if (min_value is not None and node.value <= min_value) or \
(max_value is not None and node.value >= max_value):
return False
return (is_bst_helper(node.left, min_value, node.value) and \
is_bst_helper(node.right, node.value, max_value))
return is_bst_helper(root, None, None)
def get_height(root):
if not root:
return 0
return 1 + max(get_height(root.left), get_height(root.right))
def is_balanced(root):
if not root:
return True
left_height = get_height(root.left)
right_height = get_height(root.right)
# Check if heights of left and right subtrees differ by more than 1
if abs(left_height - right_height) > 1:
return False
return is_balanced(root.left) and is_balanced(root.right)
def balance_bst_brute_force(root):
if not root:
return None
# Extract all node values from the tree
node_values = []
def inorder_traversal(node):
if node:
inorder_traversal(node.left)
node_values.append(node.value)
inorder_traversal(node.right)
inorder_traversal(root)
# Generate all possible BSTs from the values
possible_trees = generate_trees(node_values)
best_tree = None
best_balance_factor = float('inf')
for tree in possible_trees:
# Check if the tree is a valid BST
if is_bst(tree):
# Check if the tree is balanced
if is_balanced(tree):
left_height = get_height(tree.left)
right_height = get_height(tree.right)
balance_factor = abs(left_height - right_height)
# Find a more balanced BST
if balance_factor < best_balance_factor:
best_balance_factor = balance_factor
best_tree = tree
# If no valid BST found, return original root
if best_tree is None:
return root
return best_tree
The best way to balance a binary search tree is to first extract all the elements in a sorted order. Then, using this sorted list, reconstruct a perfectly balanced tree, ensuring optimal height and distribution of elements.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value=0, left=None, right=None):
self.value = value
self.left = left
self.right = right
def balance_bst(root):
def inorder_traversal(node, sorted_list):
if not node:
return
inorder_traversal(node.left, sorted_list)
sorted_list.append(node.value)
inorder_traversal(node.right, sorted_list)
sorted_nodes = []
inorder_traversal(root, sorted_nodes)
# Build balanced BST from sorted list.
def build_balanced_tree(sorted_list):
if not sorted_list:
return None
middle_index = len(sorted_list) // 2
# Root is middle element to ensure balance.
root_node = TreeNode(sorted_list[middle_index])
root_node.left = build_balanced_tree(sorted_list[:middle_index])
# Right subtree contains elements greater than root
root_node.right = build_balanced_tree(sorted_list[middle_index + 1:])
return root_node
return build_balanced_tree(sorted_nodes)
Case | How to Handle |
---|---|
Null or Empty Tree | Return null immediately as there's nothing to balance. |
Single Node Tree | Return the original node as it's already balanced. |
Tree with only left children (skewed left) | The balancing algorithm should correctly re-arrange the nodes to reduce height. |
Tree with only right children (skewed right) | The balancing algorithm must rearrange nodes to distribute them evenly and balance the tree. |
Tree with duplicate values | Ensure balancing algorithm (e.g., AVL/Red-Black) handles duplicate keys correctly without violating BST properties, potentially by adjusting insertion logic. |
Very large tree (approaching memory limits) | Consider iterative inorder traversal to avoid stack overflow, and verify algorithm scales efficiently in terms of both time and memory. |
Tree with negative and positive values | The balancing algorithm should handle both positive and negative values correctly maintaining the BST property during balancing. |
Integer overflow during value calculations (if applicable for balancing criteria) | Use appropriate data types or checks to prevent integer overflows when calculating heights or differences. |