654. Maximum Binary Tree
Problem Description
You need to build a special type of binary tree called a "maximum binary tree" from an integer array nums
that contains no duplicate values.
The construction follows these rules:
- Find the maximum value in the current array
nums
- this becomes the root node - Split the array into two parts at the position of the maximum value:
- Everything to the left of the maximum becomes the left subarray
- Everything to the right of the maximum becomes the right subarray
- Recursively build the left subtree using the left subarray (following the same rules)
- Recursively build the right subtree using the right subarray (following the same rules)
- Continue this process until all elements are placed in the tree
For example, given nums = [2, 1, 6, 3]
:
- The maximum is
6
, so it becomes the root - Left of
6
is[2, 1]
- this forms the left subtree- Maximum of
[2, 1]
is2
, which becomes the left child - Left of
2
is empty, right of2
is[1]
1
becomes the right child of2
- Maximum of
- Right of
6
is[3]
- this forms the right subtree3
becomes the right child of6
The solution uses a recursive approach where for each call:
- It finds the maximum value in the current array segment
- Creates a node with that maximum value
- Recursively constructs left and right subtrees from the appropriate array segments
- Returns the constructed tree rooted at the maximum value node
The base case occurs when the array is empty, returning None
to indicate no node should be created.
Intuition
The key insight is recognizing that this problem has a natural recursive structure. When we pick the maximum element as the root, we're essentially dividing the problem into two smaller, independent subproblems - building the left and right subtrees.
Think about it this way: once we place the maximum value at the root, all elements to its left in the original array must go into the left subtree, and all elements to its right must go into the right subtree. This is because we need to preserve the relative ordering of elements.
Why does this recursive pattern work? Consider what happens at each level:
- The maximum element naturally becomes the root because that's the rule
- The elements are already partitioned by their position relative to the maximum
- Each partition (left and right) is just a smaller version of the same problem
This leads us to a divide-and-conquer approach:
- Find the maximum (this is our pivot point)
- Divide the array at this pivot
- Conquer by recursively solving for each half
The beauty of this approach is that finding the maximum and splitting the array gives us everything we need for the current node, and we can trust recursion to handle the rest. Each recursive call handles a smaller array segment, eventually reaching empty arrays (our base case).
The pattern find max → create node → split array → recurse on both parts
naturally builds the tree structure we want, with larger values closer to the root and the original array's relative ordering preserved in the tree's in-order traversal.
Learn more about Stack, Tree, Divide and Conquer, Binary Tree and Monotonic Stack patterns.
Solution Approach
The implementation uses a recursive helper function dfs
to construct the maximum binary tree. Let's walk through the implementation step by step:
Base Case:
if not nums: return None
When the array is empty, we return None
to indicate no node should be created. This happens when we've processed all elements in a subarray.
Finding the Maximum and Its Position:
val = max(nums)
i = nums.index(val)
We find the maximum value in the current array using max(nums)
and locate its position using nums.index(val)
. This gives us both the value for our current node and the split point for dividing the array.
Creating the Current Node:
root = TreeNode(val)
We create a new tree node with the maximum value. This node will serve as the root for the current subtree.
Recursive Construction:
root.left = dfs(nums[:i]) root.right = dfs(nums[i + 1:])
nums[:i]
creates a slice containing all elements before the maximum (left subarray)nums[i + 1:]
creates a slice containing all elements after the maximum (right subarray)- We recursively call
dfs
on each subarray to build the left and right subtrees
Array Slicing Pattern: The solution uses Python's array slicing to partition the data:
- If maximum is at index
i
, elements[0...i-1]
go to the left subtree - Elements
[i+1...n-1]
go to the right subtree - The element at index
i
becomes the current node
Time Complexity: O(n²)
in the worst case (when the array is sorted), where finding the maximum takes O(n)
and we do this for each element. In the best case (balanced partitions), it's O(n log n)
.
Space Complexity: O(n)
for the recursion stack in the worst case, plus the space for array slices.
The elegance of this solution lies in its direct translation of the problem requirements into code - each recursive call handles exactly one node and delegates the subtree construction to recursive calls.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with nums = [3, 2, 1, 6, 0, 5]
to see how the maximum binary tree is constructed.
Initial Call: dfs([3, 2, 1, 6, 0, 5])
- Find max:
6
at index3
- Create root node with value
6
- Split array:
- Left subarray:
[3, 2, 1]
(indices 0-2) - Right subarray:
[0, 5]
(indices 4-5)
- Left subarray:
- Recursively build left and right subtrees
Left Subtree: dfs([3, 2, 1])
- Find max:
3
at index0
- Create node with value
3
- Split array:
- Left subarray:
[]
(empty) - Right subarray:
[2, 1]
(indices 1-2)
- Left subarray:
- Left child is
None
(empty array) - Recursively build right subtree
Processing [2, 1]
:
- Find max:
2
at index0
- Create node with value
2
- Split: left
[]
, right[1]
- Left child is
None
- Right child:
dfs([1])
creates node with value1
(no further splits needed)
Right Subtree: dfs([0, 5])
- Find max:
5
at index1
- Create node with value
5
- Split array:
- Left subarray:
[0]
(index 0) - Right subarray:
[]
(empty)
- Left subarray:
- Left child:
dfs([0])
creates node with value0
- Right child is
None
Final Tree Structure:
6 / \ 3 5 \ / 2 0 \ 1
The algorithm correctly places the maximum value 6
at the root, with all elements originally to its left ([3, 2, 1]
) in the left subtree and elements to its right ([0, 5]
) in the right subtree. This pattern continues recursively at each level, maintaining the relative ordering of the original array.
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 List, Optional
9
10class Solution:
11 def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
12 """
13 Constructs a maximum binary tree from the given array.
14 A maximum binary tree is built recursively where:
15 - The root is the maximum element in the array
16 - The left subtree is constructed from elements before the maximum
17 - The right subtree is constructed from elements after the maximum
18
19 Args:
20 nums: List of integers to construct the tree from
21
22 Returns:
23 Root node of the constructed maximum binary tree
24 """
25
26 def build_tree(arr: List[int]) -> Optional[TreeNode]:
27 """
28 Recursively builds the maximum binary tree from a subarray.
29
30 Args:
31 arr: Subarray to build the tree from
32
33 Returns:
34 Root node of the subtree, or None if array is empty
35 """
36 # Base case: empty array returns None
37 if not arr:
38 return None
39
40 # Find the maximum value and its index
41 max_value = max(arr)
42 max_index = arr.index(max_value)
43
44 # Create root node with maximum value
45 root = TreeNode(max_value)
46
47 # Recursively build left subtree from elements before max
48 root.left = build_tree(arr[:max_index])
49
50 # Recursively build right subtree from elements after max
51 root.right = build_tree(arr[max_index + 1:])
52
53 return root
54
55 # Start the recursive construction
56 return build_tree(nums)
57
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 // Array to store the input numbers
18 private int[] nums;
19
20 /**
21 * Constructs a maximum binary tree from the given array.
22 * The root is the maximum element, left subtree is built from elements to the left,
23 * and right subtree is built from elements to the right of the maximum.
24 *
25 * @param nums The input array of integers
26 * @return The root of the constructed maximum binary tree
27 */
28 public TreeNode constructMaximumBinaryTree(int[] nums) {
29 this.nums = nums;
30 return buildTree(0, nums.length - 1);
31 }
32
33 /**
34 * Recursively builds the maximum binary tree for the given range.
35 *
36 * @param left The left boundary index (inclusive)
37 * @param right The right boundary index (inclusive)
38 * @return The root node of the subtree for this range
39 */
40 private TreeNode buildTree(int left, int right) {
41 // Base case: invalid range
42 if (left > right) {
43 return null;
44 }
45
46 // Find the index of the maximum element in the current range
47 int maxIndex = left;
48 for (int currentIndex = left; currentIndex <= right; currentIndex++) {
49 if (nums[currentIndex] > nums[maxIndex]) {
50 maxIndex = currentIndex;
51 }
52 }
53
54 // Create the root node with the maximum value
55 TreeNode root = new TreeNode(nums[maxIndex]);
56
57 // Recursively build left subtree from elements before the maximum
58 root.left = buildTree(left, maxIndex - 1);
59
60 // Recursively build right subtree from elements after the maximum
61 root.right = buildTree(maxIndex + 1, right);
62
63 return root;
64 }
65}
66
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 * Constructs a maximum binary tree from the given array.
16 * The root is the maximum element, left subtree is built from elements to the left,
17 * and right subtree is built from elements to the right of the maximum.
18 *
19 * @param nums The input array to construct the tree from
20 * @return The root of the constructed maximum binary tree
21 */
22 TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
23 return buildTree(nums, 0, nums.size() - 1);
24 }
25
26private:
27 /**
28 * Recursively builds the maximum binary tree for a given range of the array.
29 *
30 * @param nums The input array
31 * @param left The left boundary of the current range (inclusive)
32 * @param right The right boundary of the current range (inclusive)
33 * @return The root node of the subtree for this range
34 */
35 TreeNode* buildTree(vector<int>& nums, int left, int right) {
36 // Base case: invalid range
37 if (left > right) {
38 return nullptr;
39 }
40
41 // Find the index of the maximum element in the current range
42 int maxIndex = left;
43 for (int i = left; i <= right; ++i) {
44 if (nums[i] > nums[maxIndex]) {
45 maxIndex = i;
46 }
47 }
48
49 // Create the root node with the maximum value
50 TreeNode* root = new TreeNode(nums[maxIndex]);
51
52 // Recursively build the left subtree from elements before the maximum
53 root->left = buildTree(nums, left, maxIndex - 1);
54
55 // Recursively build the right subtree from elements after the maximum
56 root->right = buildTree(nums, maxIndex + 1, right);
57
58 return root;
59 }
60};
61
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Constructs a maximum binary tree from an array of numbers.
17 * The root is the maximum value in the array.
18 * The left subtree is constructed from elements before the maximum.
19 * The right subtree is constructed from elements after the maximum.
20 *
21 * @param nums - Array of distinct integers
22 * @returns Root node of the constructed maximum binary tree
23 */
24function constructMaximumBinaryTree(nums: number[]): TreeNode | null {
25 // Get the length of the input array
26 const arrayLength: number = nums.length;
27
28 // Base case: empty array returns null
29 if (arrayLength === 0) {
30 return null;
31 }
32
33 // Find the maximum value and its index in the array
34 // Returns a tuple: [maxValue, maxIndex]
35 const [maxValue, maxIndex]: [number, number] = nums.reduce(
36 (accumulator: [number, number], currentValue: number, currentIndex: number): [number, number] => {
37 // Update accumulator if current value is greater than the stored max
38 return accumulator[0] < currentValue ? [currentValue, currentIndex] : accumulator;
39 },
40 [-1, 0] // Initial value: [-1, 0]
41 );
42
43 // Create the root node with the maximum value
44 // Recursively construct left subtree from elements before max
45 // Recursively construct right subtree from elements after max
46 return new TreeNode(
47 maxValue,
48 constructMaximumBinaryTree(nums.slice(0, maxIndex)),
49 constructMaximumBinaryTree(nums.slice(maxIndex + 1))
50 );
51}
52
Time and Space Complexity
Time Complexity: O(n²)
in the worst case, O(n log n)
in the average case
- In each recursive call, we find the maximum element using
max(nums)
which takesO(n)
time wheren
is the size of the current subarray - We also use
nums.index(val)
to find the index, which takes anotherO(n)
time - The list slicing operations
nums[:i]
andnums[i+1:]
each takeO(n)
time to create new lists - In the worst case (when the array is sorted), the recursion tree is skewed:
- The maximum is always at one end, creating a tree of depth
n
- At each level
k
, we processn-k
elements - Total work:
n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
- The maximum is always at one end, creating a tree of depth
- In the best/average case (balanced partitions), the recursion tree has depth
O(log n)
:- Each level of the tree processes
O(n)
total elements - With
O(log n)
levels, total complexity isO(n log n)
- Each level of the tree processes
Space Complexity: O(n²)
in the worst case, O(n log n)
in the average case
- The recursive call stack depth is
O(n)
in the worst case andO(log n)
in the average case - At each recursive call, we create new sublists through slicing:
- Each slice creates a new list that requires space proportional to its size
- In the worst case, we have
O(n)
recursive calls, and the total space for all slices isn + (n-1) + ... + 1 = O(n²)
- In the average case with balanced partitions, we have
O(log n)
levels, and each level usesO(n)
space for slices, resulting inO(n log n)
space
- Additionally, the output tree itself requires
O(n)
space to store all nodes
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Maximum Finding in Each Recursive Call
The current solution uses max(arr)
followed by arr.index(max_value)
, which requires two passes through the array - one to find the maximum and another to find its position. This is inefficient and contributes to the O(n²) worst-case complexity.
Problem Code:
max_value = max(arr)
max_index = arr.index(max_value)
Better Approach:
# Find max value and index in a single pass
max_index = 0
max_value = arr[0]
for i in range(1, len(arr)):
if arr[i] > max_value:
max_value = arr[i]
max_index = i
2. Excessive Array Copying with Slicing
Creating new array slices (arr[:max_index]
and arr[max_index + 1:]
) for each recursive call creates unnecessary copies of the data, consuming O(n) extra space per level and adding to the time complexity.
Optimized Solution Using Index Boundaries:
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
def build_tree(left: int, right: int) -> Optional[TreeNode]:
# Work with indices instead of creating new arrays
if left > right:
return None
# Find max in the range [left, right]
max_index = left
for i in range(left + 1, right + 1):
if nums[i] > nums[max_index]:
max_index = i
root = TreeNode(nums[max_index])
root.left = build_tree(left, max_index - 1)
root.right = build_tree(max_index + 1, right)
return root
return build_tree(0, len(nums) - 1)
3. Stack-Based O(n) Solution Not Considered
The recursive approach has O(n²) worst-case time complexity. There's a more efficient O(n) solution using a monotonic decreasing stack that many developers miss:
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
stack = []
for num in nums:
node = TreeNode(num)
last = None
# Pop smaller elements and make them left child
while stack and stack[-1].val < num:
last = stack.pop()
node.left = last
# Current node becomes right child of the last larger element
if stack:
stack[-1].right = node
stack.append(node)
return stack[0] if stack else None
This maintains a decreasing stack where each new element either becomes a right child of the stack top (if smaller) or pops elements to become their parent (if larger), achieving linear time complexity.
Which technique can we use to find the middle of a linked list?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!