Facebook Pixel

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:

  1. Find the maximum value in the current array nums - this becomes the root node
  2. 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
  3. Recursively build the left subtree using the left subarray (following the same rules)
  4. Recursively build the right subtree using the right subarray (following the same rules)
  5. 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] is 2, which becomes the left child
    • Left of 2 is empty, right of 2 is [1]
    • 1 becomes the right child of 2
  • Right of 6 is [3] - this forms the right subtree
    • 3 becomes the right child of 6

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Find the maximum (this is our pivot point)
  2. Divide the array at this pivot
  3. 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 Evaluator

Example 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 index 3
  • Create root node with value 6
  • Split array:
    • Left subarray: [3, 2, 1] (indices 0-2)
    • Right subarray: [0, 5] (indices 4-5)
  • Recursively build left and right subtrees

Left Subtree: dfs([3, 2, 1])

  • Find max: 3 at index 0
  • Create node with value 3
  • Split array:
    • Left subarray: [] (empty)
    • Right subarray: [2, 1] (indices 1-2)
  • Left child is None (empty array)
  • Recursively build right subtree

Processing [2, 1]:

  • Find max: 2 at index 0
  • Create node with value 2
  • Split: left [], right [1]
  • Left child is None
  • Right child: dfs([1]) creates node with value 1 (no further splits needed)

Right Subtree: dfs([0, 5])

  • Find max: 5 at index 1
  • Create node with value 5
  • Split array:
    • Left subarray: [0] (index 0)
    • Right subarray: [] (empty)
  • Left child: dfs([0]) creates node with value 0
  • 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 takes O(n) time where n is the size of the current subarray
  • We also use nums.index(val) to find the index, which takes another O(n) time
  • The list slicing operations nums[:i] and nums[i+1:] each take O(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 process n-k elements
    • Total work: n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
  • 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 is O(n log n)

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 and O(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 is n + (n-1) + ... + 1 = O(n²)
    • In the average case with balanced partitions, we have O(log n) levels, and each level uses O(n) space for slices, resulting in O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More