You are given the head
of a singly linked list where elements are sorted in ascending order. Your task is to convert this linked list into a height-balanced binary search tree (BST).
A height-balanced BST is a binary search tree where the depth of the two subtrees of every node never differs by more than 1.
Example 1:
Consider the following sorted linked list:
-10 -> -3 -> 0 -> 5 -> 9
One possible height-balanced BST that can be constructed from this linked list is:
0
/ \
-3 9
/ /
-10 5
This BST can be represented as the array [0, -3, 9, -10, null, 5]
.
Example 2:
Consider an empty linked list:
null
In this case, the output should be an empty BST, represented as []
.
Write a function that takes the head
of a sorted linked list as input and returns the root of a height-balanced BST constructed from the linked list. The solution should be efficient in terms of time and space complexity.
Constraints:
head
is in the range [0, 2 * 10^4]
.-10^5 <= Node.val <= 10^5
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:
We want to build a perfectly balanced tree from a sorted list. The brute force method tries every conceivable tree shape, checking each one to see if it meets our conditions, like being balanced and keeping the sorted order from the list.
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 convert_sorted_list_to_bst(sorted_list):
def is_binary_search_tree(root, min_value=float('-inf'), max_value=float('inf')):
if not root:
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_height_balanced(root):
def get_height(node):
if not node:
return 0
return 1 + max(get_height(node.left), get_height(node.right))
if not root:
return True
left_height = get_height(root.left)
right_height = get_height(root.right)
if abs(left_height - right_height) > 1:
return False
return is_height_balanced(root.left) and is_height_balanced(root.right)
def generate_trees(sub_list):
if not sub_list:
return [None]
trees = []
for index in range(len(sub_list)):
root_value = sub_list[index]
# Consider current element as root
left_sub_list = sub_list[:index]
right_sub_list = sub_list[index+1:]
left_subtrees = generate_trees(left_sub_list)
right_subtrees = generate_trees(right_sub_list)
# Construct all possible trees with this root
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_list)
valid_trees = []
for tree in all_possible_trees:
if is_binary_search_tree(tree) and is_height_balanced(tree):
# Check BST and height balance condition
valid_trees.append(tree)
if valid_trees:
return valid_trees[0]
# Return a valid tree
else:
return None
# No valid tree found
The best way to solve this problem is to build the tree from the middle out. This ensures we create a balanced tree directly, which is key for optimal performance. By picking the middle element as the root, we naturally divide the problem into smaller, similar subproblems.
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
class ListNode:
def __init__(self, value=0, next_node=None):
self.value = value
self.next_node = next_node
def sorted_list_to_bst(head):
def find_middle(head_node):
slow_pointer = head_node
fast_pointer = head_node
previous_node = None
while fast_pointer and fast_pointer.next_node:
previous_node = slow_pointer
slow_pointer = slow_pointer.next_node
fast_pointer = fast_pointer.next_node.next_node
return previous_node, slow_pointer
def build_bst(head_node):
if not head_node:
return None
previous_node, middle_node = find_middle(head_node)
# If list has more than one element,
# break the list into two halves
if previous_node:
previous_node.next_node = None
# The middle element is the root
root = TreeNode(middle_node.value)
# If list only had one element,
# make it the root and return.
if head_node == middle_node:
return root
# Recursively build the left subtree
root.left = build_bst(head_node)
# Recursively build the right subtree
root.right = build_bst(middle_node.next_node)
return root
return build_bst(head)
Case | How to Handle |
---|---|
Null input list | Return null, indicating an empty tree for null input. |
Empty input list | Return null, representing an empty tree. |
List with a single node | Create a single node BST with the value of that node from the list. |
List with two nodes | Create a BST with the first node as the root, and the second as the right child. |
List with many nodes (scalability) | The algorithm should use recursion, which may lead to stack overflow; an iterative approach might be preferable for extremely large lists. |
List with duplicate values | The algorithm should correctly construct a BST regardless of duplicate values in the list. |
List with negative and positive values | The algorithm should correctly handle both negative and positive values without any special modifications. |
Skewed list (e.g., almost sorted, or almost reverse sorted) | The resulting BST might be highly unbalanced if the list is skewed, potentially leading to O(n) search time in worst-case scenarios, and balancing algorithms might be needed. |