Given an integer array nums
where the elements are sorted in ascending order, convert it to a height-balanced binary search tree. A height-balanced binary search tree is defined as a binary tree in which the left and right subtrees of every node have a height difference of no more than 1.
Example 1:
Input: nums = [-10, -3, 0, 5, 9]
Output: [0, -3, 9, -10, null, 5]
Explanation: One possible solution is [0, -10, 5, null, -3, null, 9]
. Another acceptable solution is [0, -3, 9, -10, null, 5]
.
Example 2:
Input: nums = [1, 3]
Output: [3, 1]
Explanation: Both [1, null, 3]
and [3, 1]
are valid height-balanced BSTs.
Can you provide an algorithm and code to solve this problem efficiently? Discuss the time and space complexity of your solution. Also, consider and address any edge cases.
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 goal is to make a perfectly balanced tree from an ordered list. The brute force way is to try every single possible tree shape you can make with the numbers given.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def is_binary_search_tree(root, min_value=float('-inf'), max_value=float('inf')):
if root is None:
return True
if root.value <= min_value or root.value >= max_value:
return False
return (is_binary_search_tree(root.left, min_value, root.value) and\
is_binary_search_tree(root.right, root.value, max_value))
def is_balanced(root):
if root is None:
return True
def get_height(node):
if node is None:
return 0
return 1 + max(get_height(node.left), get_height(node.right))
left_height = get_height(root.left)
right_height = get_height(root.right)
if abs(left_height - right_height) > 1:
return False
return is_balanced(root.left) and is_balanced(root.right)
def convert_sorted_array_to_binary_search_tree_brute_force(sorted_array):
best_tree = None
def generate_trees(numbers):
if not numbers:
return [None]
trees = []
for index in range(len(numbers)):
# Consider each number as the root.
root_value = numbers[index]
left_sub_array = numbers[:index]
right_sub_array = numbers[index+1:]
left_subtrees = generate_trees(left_sub_array)
right_subtrees = generate_trees(right_sub_array)
# Try every combination of subtrees
for left_subtree in left_subtrees:
for right_subtree in right_subtrees:
root = TreeNode(root_value)
root.left = left_subtree
root.right = right_subtree
trees.append(root)
return trees
all_possible_trees = generate_trees(sorted_array)
#Iterate and evaluate whether a tree fits the given criteria
for possible_tree in all_possible_trees:
if is_binary_search_tree(possible_tree) and is_balanced(possible_tree):
best_tree = possible_tree
return best_tree
return None
The core idea is to build the tree from the middle outward. This ensures that the tree stays balanced, which leads to optimal performance. By always picking the middle as the root we guarantee we are building the most balanced structure possible at each step.
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 sortedArrayToBST(nums):
def construct_bst(left_index, right_index):
if left_index > right_index:
return None
# Find the middle index to ensure balanced tree structure
middle_index = (left_index + right_index) // 2
root_node = TreeNode(nums[middle_index])
# Recursively build the left subtree
root_node.left = construct_bst(left_index, middle_index - 1)
# Recursively build the right subtree
root_node.right = construct_bst(middle_index + 1, right_index)
return root_node
# Start with the entire array
return construct_bst(0, len(nums) - 1)
Case | How to Handle |
---|---|
Empty input array | Return null or None, indicating an empty tree. |
Array with a single element | Create a root node with the single element and return it. |
Array with two elements | Choose either element as root and the other as its left or right child, maintaining BST property. |
Large array (stress test) | The algorithm should have logarithmic height, preventing stack overflow with recursion and scaling efficiently. |
Array with all negative numbers | The algorithm should handle negative numbers correctly due to the sorted nature of the input. |
Array with extremely large or small numbers (integer overflow) | The code should be written to avoid integer overflow during calculations of the middle index, potentially using long types. |
Input array containing zero | Zero should be handled like any other integer according to its sorted position. |
Array close to maximum integer size | Midpoint calculation should not cause overflow, which can be avoided by using start + (end - start) / 2. |