A maximum tree is a tree where every node has a value greater than any other value in its subtree.
You are given the root of a maximum binary tree and an integer val.
Just as in the previous problem, the given tree was constructed from a list a (root = Construct(a)) recursively with the following Construct(a) routine:
a is empty, return null.a[i] be the largest element of a. Create a root node with the value a[i].root will be Construct([a[0], a[1], ..., a[i - 1]]).root will be Construct([a[i + 1], a[i + 2], ..., a[a.length - 1]]).root.Note that we were not given a directly, only a root node root = Construct(a).
Suppose b is a copy of a with the value val appended to it. It is guaranteed that b has unique values.
Return Construct(b).
Example 1:
Input: root = [4,1,3,null,null,2], val = 5 Output: [5,4,null,1,3,null,null,2] Explanation: a = [1,4,2,3], b = [1,4,2,3,5]
Example 2:
Input: root = [5,2,4,null,1], val = 3 Output: [5,2,4,null,1,null,3] Explanation: a = [2,1,5,4], b = [2,1,5,4,3]
Example 3:
Input: root = [5,2,3,null,1], val = 4 Output: [5,2,4,null,1,3] Explanation: a = [2,1,5,3], b = [2,1,5,3,4]
Constraints:
[1, 100].1 <= Node.val <= 1001 <= val <= 100When 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:
With the brute force method, we're exploring every possible way to insert the new value into the binary tree. We build a new tree for each possibility and then compare them to see which one fits the requirements of a maximum binary tree.
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 construct_maximum_binary_tree_brute_force(root, value_to_insert):
possible_trees = []
# Inserting new node as root.
new_root = TreeNode(value_to_insert, root, None)
if is_maximum_binary_tree(new_root):
possible_trees.append(new_root)
# Helper function to generate trees by insertion
def generate_trees(node, value_to_insert):
if not node:
return [None]
# Insert to the left
old_left = node.left
node.left = TreeNode(value_to_insert, old_left, None)
if is_maximum_binary_tree(root):
possible_trees.append(copy_tree(root))
node.left = old_left # revert
# Insert to the right
old_right = node.right
node.right = TreeNode(value_to_insert, old_right, None)
if is_maximum_binary_tree(root):
possible_trees.append(copy_tree(root))
node.right = old_right # revert
generate_trees(node.left, value_to_insert)
generate_trees(node.right, value_to_insert)
generate_trees(root, value_to_insert)
# Return the first valid tree or None if none is valid
if not possible_trees:
return None
return possible_trees[0]
def is_maximum_binary_tree(root):
if not root:
return True
if root.left and root.value <= root.left.value:
return False
if root.right and root.value <= root.right.value:
return False
return is_maximum_binary_tree(root.left) and is_maximum_binary_tree(root.right)
def copy_tree(root):
if not root:
return None
new_node = TreeNode(root.value)
new_node.left = copy_tree(root.left)
new_node.right = copy_tree(root.right)
return new_node
# Below is test scaffolding - not part of the solution
# Create the tree: [3,2,1,null,null,null,null]
# root = TreeNode(3)
# root.left = TreeNode(2)
# root.right = TreeNode(1)
#
# new_root = construct_maximum_binary_tree_brute_force(root, 5)
#
# def print_tree(root):
# if root:
# print(root.value)
# print_tree(root.left)
# print_tree(root.right)
# print_tree(new_root)The problem involves inserting a new value into an existing 'maximum binary tree' while maintaining its structure. The core idea is to leverage the properties of maximum binary trees: each node is larger than its descendants. By understanding that, we can find the correct insertion point quickly.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def insertIntoMaxTree(root, value_to_insert):
new_node = TreeNode(value_to_insert)
# If the new value is greater than the root,
# it becomes the new root.
if not root or value_to_insert > root.val:
new_node.left = root
return new_node
current_node = root
# Traverse down the right subtree to find
# the correct insertion point
while current_node.right:
if value_to_insert > current_node.right.val:
new_node.left = current_node.right
current_node.right = new_node
return root
current_node = current_node.right
# If we reach the end of the right subtree,
# insert the new node here.
current_node.right = new_node
return root| Case | How to Handle |
|---|---|
| Null root or null node to insert | Handle null root by creating a new tree with the new node as the root, and handle null node-to-insert as invalid input |
| Empty Tree (root is None) inserting at root | Create a new TreeNode as the root and return it, making it the new root of the tree. |
| Inserting a value smaller than the minimum value in the tree | Insert the node at the leftmost position to maintain the BST property, as it will be the smallest element. |
| Inserting a value larger than the maximum value in the tree | Insert the node at the rightmost position to maintain the BST property, as it will be the largest element. |
| Tree with only one node and inserting smaller/larger value | Insert the new node as the left/right child respectively, handling both cases to preserve BST properties |
| Inserting a duplicate value; BST with duplicates allowed (insert on the right to avoid infinite loop) | Insert the duplicate node to the right of the existing node to avoid infinite loops and maintain a relatively balanced tree in the presence of duplicates. |
| Integer overflow in large or deeply skewed trees | Use appropriate data types (e.g., long) and consider using iterative insertion to avoid potential stack overflow with very deep trees. |
| Tree structure severely unbalanced after multiple insertions causing O(n) lookup | Consider using self-balancing tree structures (e.g., AVL, Red-Black) for better performance on average and worst-case insert |