998. Maximum Binary Tree II
Problem Description
You're given a maximum binary tree and need to insert a new value into it. A maximum tree is a special binary tree where every node's value is greater than all values in its subtree.
The tree was originally built from an array using this construction process:
- Find the largest element in the array and make it the root
- Elements to the left of the maximum form the left subtree (built recursively)
- Elements to the right of the maximum form the right subtree (built recursively)
Now you need to insert a new value val
at the end of the original array and rebuild the tree following the same rules.
Key insight: Since val
is appended to the end of the array, it affects how the tree is reconstructed:
- If
val
is larger than the current root, it becomes the new root with the entire original tree as its left child - If
val
is smaller than the current root, it must be inserted somewhere in the right subtree (since it comes after all existing elements in the array)
The solution uses recursion to handle these two cases:
- If
root
isNone
orroot.val < val
: Create a new node withval
as the root and the current tree as its left child - Otherwise: Recursively insert
val
into the right subtree
This maintains the maximum tree property while respecting the position of val
at the end of the original array.
Intuition
Let's think about what happens when we append a value to the original array and reconstruct the tree.
Since val
is added at the end of the array, it's the rightmost element. When we apply the construction algorithm, we need to consider where this new element fits in the tree structure.
Consider two scenarios:
Scenario 1: val
is the largest element in the entire array (including the new value).
- The construction algorithm would pick
val
as the root - Everything to its left (the entire original array) becomes the left subtree
- There's nothing to its right, so no right subtree
Scenario 2: val
is not the largest element.
- The root remains the same (still the maximum from the original array)
- Since
val
comes after the root element in the array, it must be part of the right subtree - We recursively apply the same logic to the right subtree
The key realization is that we're essentially finding the correct position for val
by traversing down the right spine of the tree. We keep going right until we find a node whose value is less than val
. At that point, val
becomes the new parent of that node.
Why only traverse right? Because val
is appended at the end of the array, it can never be in anyone's left subtree - left subtrees are built from elements that come before the root in the array, but val
comes after everything else.
This leads to the elegant recursive solution: either val
becomes the new root (if it's larger than the current root), or we recursively insert it into the right subtree.
Learn more about Tree and Binary Tree patterns.
Solution Approach
The solution implements a recursive approach that handles two key cases based on comparing val
with the current node's value.
Base Case & Case 1: When val
is larger than the current root
if root is None or root.val < val: return TreeNode(val, root)
- If we've reached a
None
node (empty subtree) or the current root's value is less thanval
- Create a new node with
val
as its value - Make the entire current subtree the left child of this new node
- Return this new node as the new root of this subtree
Case 2: When val
is smaller than the current root
root.right = self.insertIntoMaxTree(root.right, val) return root
- The current root remains unchanged
- Recursively insert
val
into the right subtree - Update the root's right child with the result of the recursive call
- Return the root (structure preserved except for the modified right subtree)
Why this works:
- The algorithm maintains the maximum tree property: when
val
becomes a new root, it's guaranteed to be larger than everything in its left subtree (the old tree) - By only traversing and modifying the right path, we respect the original array ordering where
val
was appended at the end - The recursion naturally finds the correct insertion point - the first node smaller than
val
along the right spine
Time Complexity: O(h)
where h
is the height of the tree. In the worst case (skewed tree), this could be O(n)
.
Space Complexity: O(h)
for the recursion stack, which could be O(n)
in the worst case.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through inserting val = 4
into this maximum tree:
Initial tree: Array representation: [4, 1, 3, null, null, 2] 5 Original array that built this: [1, 5, 3, 2] / \ We want to insert: val = 4 1 3 / 2
Step 1: Start at root (5)
- Compare:
5 < 4
? No, 5 is not less than 4 - Since 4 is smaller than 5, we need to insert 4 into the right subtree
- Call
insertIntoMaxTree(root.right, 4)
whereroot.right
is node 3
Step 2: Now at node 3
- Compare:
3 < 4
? Yes! - Since 3 is less than 4, we create a new node with value 4
- The entire subtree rooted at 3 becomes the left child of this new node 4
- Return this new node 4
Step 3: Back to root (5)
- Update
root.right
with the returned node 4 - The final tree structure:
Final tree: New array representation: [1, 5, 3, 2, 4]
5
/ \
1 4 ← 4 becomes the new right child of 5
/
3 ← The entire subtree (3 and its children)
/ becomes the left child of 4
2
Let's verify this makes sense with the original array construction:
- Original array:
[1, 5, 3, 2]
→ After insertion:[1, 5, 3, 2, 4]
- 5 is still the maximum, so it remains the root
- Elements left of 5:
[1]
forms the left subtree - Elements right of 5:
[3, 2, 4]
forms the right subtree- In
[3, 2, 4]
, the maximum is 4, so 4 becomes the root of this subtree - Elements left of 4:
[3, 2]
forms 4's left subtree (with 3 as root, 2 as its left child) - Elements right of 4: none, so no right subtree
- In
The algorithm correctly found that 4 should be inserted as the parent of node 3, maintaining both the maximum tree property and the array ordering constraint.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9
10
11class Solution:
12 def insertIntoMaxTree(
13 self, root: Optional[TreeNode], val: int
14 ) -> Optional[TreeNode]:
15 """
16 Insert a value into a maximum binary tree.
17
18 A maximum binary tree is built from an array where:
19 - The root is the maximum element in the array
20 - The left subtree is built from elements to the left of the maximum
21 - The right subtree is built from elements to the right of the maximum
22
23 When inserting a new value, it follows the property that the new value
24 is appended to the original array that built the tree.
25
26 Args:
27 root: The root node of the maximum binary tree
28 val: The value to be inserted
29
30 Returns:
31 The root of the modified maximum binary tree
32 """
33 # Case 1: Empty tree or new value is greater than root
34 # The new value becomes the new root with the old tree as its left child
35 if root is None or root.val < val:
36 return TreeNode(val, root)
37
38 # Case 2: New value is smaller than root
39 # Since the value is appended to the end of the array,
40 # it must go to the right subtree
41 root.right = self.insertIntoMaxTree(root.right, val)
42
43 return root
44
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Inserts a value into a maximum binary tree.
19 * In a maximum tree, every node's value is greater than all values in its subtree.
20 * The new value is always inserted as far right as possible while maintaining the max tree property.
21 *
22 * @param root The root of the maximum binary tree
23 * @param val The value to be inserted
24 * @return The root of the modified maximum binary tree
25 */
26 public TreeNode insertIntoMaxTree(TreeNode root, int val) {
27 // Base case: If root is null or the new value is greater than root's value,
28 // create a new node with the given value and make the current tree its left subtree
29 if (root == null || root.val < val) {
30 return new TreeNode(val, root, null);
31 }
32
33 // Recursive case: If the current root's value is greater than or equal to val,
34 // insert the value into the right subtree (maintaining the property that
35 // values are inserted as far right as possible)
36 root.right = insertIntoMaxTree(root.right, val);
37
38 // Return the root of the modified tree
39 return root;
40 }
41}
42
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Inserts a value into a maximum binary tree.
16 * In a maximum tree, every node's value is greater than all values in its subtree.
17 * The new value is inserted to maintain the maximum tree property.
18 *
19 * @param root The root of the maximum binary tree
20 * @param val The value to be inserted
21 * @return The root of the modified maximum binary tree
22 */
23 TreeNode* insertIntoMaxTree(TreeNode* root, int val) {
24 // Base case: If root is null or the new value is greater than root's value,
25 // create a new node with the given value and make the current tree its left subtree
26 if (root == nullptr || root->val < val) {
27 return new TreeNode(val, root, nullptr);
28 }
29
30 // Recursive case: If the current root's value is greater than or equal to val,
31 // recursively insert the value into the right subtree
32 // (This maintains the property that values are inserted in the order they appear)
33 root->right = insertIntoMaxTree(root->right, val);
34
35 // Return the root of the modified tree
36 return root;
37 }
38};
39
1/**
2 * Definition for a binary tree node.
3 * Since we cannot define a class, we'll use a type/interface
4 */
5type TreeNode = {
6 val: number;
7 left: TreeNode | null;
8 right: TreeNode | null;
9};
10
11/**
12 * Creates a new TreeNode with the given value and optional left/right children
13 * @param val - The value of the node (default: 0)
14 * @param left - The left child node (default: null)
15 * @param right - The right child node (default: null)
16 * @returns A new TreeNode instance
17 */
18function createTreeNode(val: number = 0, left: TreeNode | null = null, right: TreeNode | null = null): TreeNode {
19 return {
20 val: val,
21 left: left,
22 right: right
23 };
24}
25
26/**
27 * Inserts a new value into a maximum binary tree.
28 *
29 * In a maximum tree, every node's value is greater than all values in its subtree.
30 * The insertion follows these rules:
31 * 1. If the root is null or the new value is greater than root's value,
32 * create a new node with the value and make the entire tree its left subtree
33 * 2. Otherwise, recursively insert the value into the right subtree
34 *
35 * @param root - The root node of the maximum binary tree
36 * @param val - The value to be inserted into the tree
37 * @returns The root of the modified maximum binary tree
38 */
39function insertIntoMaxTree(root: TreeNode | null, val: number): TreeNode | null {
40 // Base case: if root is null or the new value is greater than root value
41 // Create a new node as the new root with the current tree as its left subtree
42 if (!root || root.val < val) {
43 return createTreeNode(val, root, null);
44 }
45
46 // Recursive case: insert the value into the right subtree
47 // The value must be inserted in the right subtree to maintain the max tree property
48 root.right = insertIntoMaxTree(root.right, val);
49
50 // Return the modified root
51 return root;
52}
53
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the tree.
In the worst case, when the value val
needs to be inserted at the rightmost position of the tree (i.e., when val
is smaller than all existing node values), the algorithm traverses from the root down the right spine of the tree. Since the tree is constructed from a maximum array where each node is the maximum of its subtree, the worst case involves visiting all nodes along the rightmost path. In a skewed tree, this could be all n
nodes, resulting in O(n)
time complexity.
Space Complexity: O(n)
where n
is the number of nodes in the tree.
The space complexity is determined by the recursive call stack. In the worst case, when the tree is completely skewed to the right (forming a linked list-like structure), the recursion depth equals the number of nodes n
. Each recursive call uses O(1)
space for its local variables, but the call stack can grow up to O(n)
levels deep, resulting in O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Inserting into the Left Subtree
The Mistake:
A common error is thinking that when val
is smaller than the root, we should check both left and right subtrees to find the appropriate insertion point, similar to a binary search tree insertion.
# INCORRECT approach
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
return TreeNode(val, root)
# Wrong: Checking left subtree
if root.left and root.left.val < val:
root.left = self.insertIntoMaxTree(root.left, val)
else:
root.right = self.insertIntoMaxTree(root.right, val)
return root
Why This is Wrong:
The key constraint is that val
is appended to the END of the original array. All elements in the left subtree came BEFORE the root in the original array, while val
comes AFTER everything. Therefore, val
can never be inserted into the left subtree.
Correct Solution:
Always insert into the right subtree when val
is smaller than the current root:
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
return TreeNode(val, root)
# Always go right when val is smaller
root.right = self.insertIntoMaxTree(root.right, val)
return root
Pitfall 2: Forgetting to Update the Parent's Reference
The Mistake: Simply calling the recursive function without updating the parent node's reference:
# INCORRECT: Not updating the reference
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
return TreeNode(val, root)
# Wrong: Not updating root.right with the result
self.insertIntoMaxTree(root.right, val) # Result is lost!
return root
Why This is Wrong:
When the recursive call creates a new subtree structure (especially when val
becomes a new root of a subtree), we need to update the parent's reference to point to this new structure. Without the assignment, the tree modification is lost.
Correct Solution: Always assign the result of the recursive call back to the parent's child reference:
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
return TreeNode(val, root)
# Correct: Update root.right with the new subtree
root.right = self.insertIntoMaxTree(root.right, val)
return root
Pitfall 3: Misunderstanding the Tree Construction When val > root
The Mistake:
When creating a new root because val
is larger, incorrectly placing the old tree as the RIGHT child:
# INCORRECT placement
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
# Wrong: Making old tree the right child
return TreeNode(val, None, root)
root.right = self.insertIntoMaxTree(root.right, val)
return root
Why This is Wrong:
In the original array, all existing elements come BEFORE the new val
. When val
becomes the new maximum (root), all previous elements should be in its LEFT subtree, not right.
Correct Solution: The old tree becomes the LEFT child of the new root:
def insertIntoMaxTree(self, root, val):
if root is None or root.val < val:
# Correct: Old tree becomes left child
return TreeNode(val, root, None) # or TreeNode(val, root)
root.right = self.insertIntoMaxTree(root.right, val)
return root
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!