Given the root
of a binary search tree, return a balanced binary search tree with the same node values. If there is more than one answer, return any of them.
A binary search tree is balanced if the depth of the two subtrees of every node never differs by more than 1
.
For example:
Input: [1,null,2,null,3,null,4,null,null]
Output: [2,1,3,null,null,null,4]
Explanation: This is not the only correct answer, [3,1,4,null,2]
is also correct.
Input: [2,1,3]
Output: [2,1,3]
Constraints:
[1, 10^4]
.Node.val <= 10^5How would you approach this problem? Consider the efficiency of your solution in terms of both time and space complexity. Are there any edge cases that need to be considered?
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. |