Maximum Binary Tree solution and explanation
9 more videos on this problem
Maximum Binary Tree (LeetCode 654) | Full Solution with a natural way to approach | RecursionNikhil Lohia · 11:54 · 6K views
花花酱 LeetCode 654. Maximum Binary Tree - 刷题找工作 EP36Hua Hua · 11:11 · 5K views
Maximum Binary Tree - LeetCode Problem #654GuyWho LikesCS · 31:01 · 3K views
LeetCode 654. Maximum Binary TreeGot Up Late · 5:17 · 2K views
654. Maximum Binary Tree (Leetcode Medium)Programming Live with Larry · 7:47 · 506 views
Construct Max Binary Tree Leetcode 654 c#Sagar Patel · 5:44 · 418 views
Leetcode 654 Maximum Binary Tree Lintcode 1106 · Maximum Binary Tree - Microsoft QuestionAlgorithmicIQ · 9:14 · 398 views
LeetCode 654 | Maximum Binary Tree | Recursion | JavaSleepy Cracker · 3:33 · 393 views
Leetcode 654. Maximum Binary Tree. Recursion.Shen Zheng · 4:09 · 315 views
Leetcode 654 | Maximum Binary Tree | DFSCoding Together · 13:52 · 261 views
The problem
You are given an integer array nums with no duplicates. A maximum binary tree can be built recursively from nums using the following algorithm:
- Create a root node whose value is the maximum value in
nums. - Recursively build the left subtree on the subarray prefix to the left of the maximum value.
- Recursively build the right subtree on the subarray suffix to the right of the maximum value.
Return the maximum binary tree built from nums.
Example 1:
Input: nums = [3,2,1,6,0,5]
Output: [6,3,5,null,2,0,null,null,1]
Explanation: The recursive calls are as follow:
- The largest value in [3,2,1,6,0,5] is 6. Left prefix is [3,2,1] and right suffix is [0,5].
- The largest value in [3,2,1] is 3. Left prefix is [] and right suffix is [2,1].
- Empty array, so no child.
- The largest value in [2,1] is 2. Left prefix is [] and right suffix is [1].
- Empty array, so no child.
- Only one element, so child is a node with value 1.
- The largest value in [0,5] is 5. Left prefix is [0] and right suffix is [].
- Only one element, so child is a node with value 0.
- Empty array, so no child.
Example 2:
Input: nums = [3,2,1] Output: [3,null,2,null,1]
Constraints:
1 <= nums.length <= 10000 <= nums[i] <= 1000- All integers in
numsare unique.
Approach overview
Problem Overview: Given an integer array with no duplicates, construct a binary tree where the root is the maximum value in the array. The left subtree is built from elements left of the maximum, and the right subtree from elements on the right. This recursive definition creates what’s called a Maximum Binary Tree.
Approach 1: Recursive Division (Divide and Conquer) (Time: O(n^2) worst, Space: O(n))
The definition of the tree directly suggests a divide and conquer strategy. Scan the current subarray to find the maximum element. Create a node with that value, then recursively build the left subtree from elements before the maximum and the right subtree from elements after it. Each recursive call reduces the problem size until the subarray becomes empty. The drawback is that finding the maximum requires a full scan each time, which leads to O(n^2) time when the array is sorted in decreasing or increasing order. Space complexity is O(n) due to recursion depth and the tree structure.
Approach 2: Monotonic Stack (Time: O(n), Space: O(n))
A more efficient approach uses a monotonic stack. Iterate through the array and maintain a decreasing stack of tree nodes. For each new value, create a node and pop all smaller nodes from the stack. The last popped node becomes the left child of the current node because it is the largest smaller element to its left. If the stack still contains elements, the top node becomes the parent and the current node becomes its right child. Push the current node onto the stack and continue. Each element is pushed and popped at most once, giving linear O(n) time. The stack stores at most n nodes, so space complexity is O(n). This approach constructs the same binary tree without repeatedly scanning subarrays.
Recommended for interviews: Interviewers usually expect the monotonic stack solution because it reduces the naive recursive O(n^2) approach to optimal O(n). Starting with the recursive definition demonstrates understanding of the problem structure, then optimizing with a monotonic stack shows algorithmic maturity and familiarity with common stack patterns.
Approach 1: Recursive Division
Recursive Division Approach: This approach involves breaking down the problem using recursive calls. We start by identifying the maximum element in the array, which becomes the root of the tree. We then recursively split the array into sub-arrays on the left and right of the maximum element and construct the left and right subtrees respectively. This leverages the divide-and-conquer paradigm.
from typing import List
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
if not nums:
return None
max_idx = nums.index(max(nums))
root = TreeNode(nums[max_idx])
root.left = self.constructMaximumBinaryTree(nums[:max_idx])
root.right = self.constructMaximumBinaryTree(nums[max_idx + 1:])
return rootclass TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return construct(nums, 0, nums.length);
}
private TreeNode construct(int[] nums, int left, int right) {
if (left == right) return null;
int maxIndex = left;
for (int i = left + 1; i < right; i++) {
if (nums[i] > nums[maxIndex]) maxIndex = i;
}
TreeNode root = new TreeNode(nums[maxIndex]);
root.left = construct(nums, left, maxIndex);
root.right = construct(nums, maxIndex + 1, right);
return root;
}
}#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if (nums.empty()) return NULL;
auto max_it = max_element(nums.begin(), nums.end());
TreeNode* root = new TreeNode(*max_it);
vector<int> left(nums.begin(), max_it);
vector<int> right(max_it + 1, nums.end());
root->left = constructMaximumBinaryTree(left);
root->right = constructMaximumBinaryTree(right);
return root;
}function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
var constructMaximumBinaryTree = function(nums) {
if (nums.length === 0) return null;
const maxIndex = nums.reduce((maxIdx, num, idx) => num > nums[maxIdx] ? idx : maxIdx, 0);
const root = new TreeNode(nums[maxIndex]);
root.left = constructMaximumBinaryTree(nums.slice(0, maxIndex));
root.right = constructMaximumBinaryTree(nums.slice(maxIndex + 1));
return root;
};#include <stdio.h>
#include <stdlib.h>
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
struct TreeNode* constructMaximumBinaryTree(int* nums, int numsSize) {
if (numsSize == 0) return NULL;
int maxIdx = 0;
for (int i = 1; i < numsSize; i++) {
if (nums[i] > nums[maxIdx]) maxIdx = i;
}
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = nums[maxIdx];
root->left = constructMaximumBinaryTree(nums, maxIdx);
root->right = constructMaximumBinaryTree(nums + maxIdx + 1, numsSize - maxIdx - 1);
return root;
}public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) { val = x; }
}
public class Solution {
public TreeNode ConstructMaximumBinaryTree(int[] nums) {
return Construct(nums, 0, nums.Length);
}
private TreeNode Construct(int[] nums, int left, int right) {
if (left == right) return null;
int maxIndex = left;
for (int i = left + 1; i < right; i++) {
if (nums[i] > nums[maxIndex]) maxIndex = i;
}
TreeNode root = new TreeNode(nums[maxIndex]);
root.left = Construct(nums, left, maxIndex);
root.right = Construct(nums, maxIndex + 1, right);
return root;
}
}This Python solution constructs the maximum binary tree using Pyhton lists and recursion. max function is used to determine the maximum element's index, and slicing helps in defining the subarrays for left and right children, thereby recursively constructing the tree nodes.
Time Complexity: O(n^2) as the max calculation is O(n) and done n times. Space Complexity: O(n) due to list slices and recursion depth.
Approach 2: Monotonic Stack
Monotonic Stack Approach: This approach involves utilizing a stack to directly simulate the necessary operations required to construct the maximum binary tree without recursion. By pushing and popping elements based on comparisons with the current element, we maintain a monotonic sequence from which the tree is constructed. This method leverages the stack to ensure that nodes are connected efficiently without the overhead of recursive calls.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def constructMaximumBinaryTree(self, nums):
stack = []
for num in nums:
current = TreeNode(num)
while stack and stack[-1].val < num:
current.left = stack.pop()
if stack:
stack[-1].right = current
stack.append(current)
return stack[0]import java.util.Stack;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
Stack<TreeNode> stack = new Stack<>();
for (int num : nums) {
TreeNode curr = new TreeNode(num);
while (!stack.isEmpty() && stack.peek().val < num) {
curr.left = stack.pop();
}
if (!stack.isEmpty()) {
stack.peek().right = curr;
}
stack.push(curr);
}
while (stack.size() > 1) stack.pop();
return stack.pop();
}
}#include <vector>
#include <stack>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
stack<TreeNode*> stk;
for (int num : nums) {
TreeNode* current = new TreeNode(num);
while (!stk.empty() && stk.top()->val < num) {
current->left = stk.top();
stk.pop();
}
if (!stk.empty()) {
stk.top()->right = current;
}
stk.push(current);
}
while (stk.size() > 1) stk.pop();
return stk.top();
}function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
var constructMaximumBinaryTree = function(nums) {
const stack = [];
for (const num of nums) {
const current = new TreeNode(num);
while (stack.length > 0 && stack[stack.length - 1].val < num) {
current.left = stack.pop();
}
if (stack.length > 0) {
stack[stack.length - 1].right = current;
}
stack.push(current);
}
return stack[0];
};#include <stdio.h>
#include <stdlib.h>
struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
};
struct TreeNode* constructMaximumBinaryTree(int* nums, int numsSize) {
struct TreeNode** stack = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * numsSize);
int stackSize = 0;
for (int i = 0; i < numsSize; ++i) {
struct TreeNode* current = (struct TreeNode*)malloc(sizeof(struct TreeNode));
current->val = nums[i];
current->left = NULL;
current->right = NULL;
while (stackSize > 0 && stack[stackSize - 1]->val < nums[i]) {
current->left = stack[stackSize - 1];
stackSize--;
}
if (stackSize > 0) {
stack[stackSize - 1]->right = current;
}
stack[stackSize++] = current;
}
while (stackSize > 1) {
stackSize--;
}
struct TreeNode* root = stack[0];
free(stack);
return root;
}public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) { val = x; }
}
public class Solution {
public TreeNode ConstructMaximumBinaryTree(int[] nums) {
Stack<TreeNode> stack = new Stack<TreeNode>();
foreach (int num in nums) {
TreeNode current = new TreeNode(num);
while (stack.Count > 0 && stack.Peek().val < num) {
current.left = stack.Pop();
}
if (stack.Count > 0) {
stack.Peek().right = current;
}
stack.Push(current);
}
while (stack.Count > 1) stack.Pop();
return stack.Pop();
}
}Description: This Python version uses a stack to maintain the tree structure as the list is processed. Each number is made the current node, and nodes are popped from the stack and linked as left children if they are smaller, leveraging stack properties to form the required structure.
Time Complexity: O(n) facilitated by single traversal and using a stack. Space Complexity: O(n) due to stack storage requirements for tree construction.
Approach 3: Default Approach
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function constructMaximumBinaryTree(nums: number[]): TreeNode | null {
const n = nums.length;
if (n === 0) {
return null;
}
const [val, i] = nums.reduce((r, v, i) => (r[0] < v ? [v, i] : r), [-1, 0]);
return new TreeNode(
val,
constructMaximumBinaryTree(nums.slice(0, i)),
constructMaximumBinaryTree(nums.slice(i + 1)),
);
}/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func constructMaximumBinaryTree(nums []int) *TreeNode {
var dfs func(l, r int) *TreeNode
dfs = func(l, r int) *TreeNode {
if l > r {
return nil
}
i := l
for j := l; j <= r; j++ {
if nums[i] < nums[j] {
i = j
}
}
root := &TreeNode{Val: nums[i]}
root.Left = dfs(l, i-1)
root.Right = dfs(i+1, r)
return root
}
return dfs(0, len(nums)-1)
}// Definition for a binary tree node.
// #[derive(Debug, PartialEq, Eq)]
// pub struct TreeNode {
// pub val: i32,
// pub left: Option<Rc<RefCell<TreeNode>>>,
// pub right: Option<Rc<RefCell<TreeNode>>>,
// }
//
// impl TreeNode {
// #[inline]
// pub fn new(val: i32) -> Self {
// TreeNode {
// val,
// left: None,
// right: None
// }
// }
// }
use std::cell::RefCell;
use std::rc::Rc;
impl Solution {
fn construct(nums: &Vec<i32>, start: usize, end: usize) -> Option<Rc<RefCell<TreeNode>>> {
if start >= end {
return None;
}
let mut idx = 0;
let mut max_val = -1;
for i in start..end {
if nums[i] > max_val {
idx = i;
max_val = nums[i];
}
}
Some(Rc::new(RefCell::new(TreeNode {
val: max_val,
left: Self::construct(nums, start, idx),
right: Self::construct(nums, idx + 1, end),
})))
}
pub fn construct_maximum_binary_tree(nums: Vec<i32>) -> Option<Rc<RefCell<TreeNode>>> {
Self::construct(&nums, 0, nums.len())
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Recursive Division | Time Complexity: O(n^2) as the max calculation is O(n) and done n times. Space Complexity: O(n) due to list slices and recursion depth. |
| Monotonic Stack | Time Complexity: O(n) facilitated by single traversal and using a stack. Space Complexity: O(n) due to stack storage requirements for tree construction. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Recursive Division (Divide and Conquer) | O(n^2) worst case | O(n) | Good for understanding the recursive definition of the tree and for quick implementation in interviews |
| Monotonic Stack | O(n) | O(n) | Optimal solution when building the tree efficiently from a single array pass |
Frequently asked questions
Is Maximum Binary Tree easy or hard?
Maximum Binary Tree is rated Medium difficulty on LeetCode. The recursive solution is straightforward once the definition is understood, but identifying and implementing the optimal monotonic stack solution requires stronger algorithmic insight.
Maximum Binary Tree Python/Java solution
Python and Java implementations typically follow either recursive divide and conquer or the monotonic stack approach. The recursive version is shorter and closely mirrors the problem definition, while the stack-based version achieves optimal O(n) time and is preferred for large inputs.
How to solve Maximum Binary Tree in O(n)?
Use a monotonic decreasing stack. Iterate through the array, popping smaller elements and assigning them as the left child of the current node. If a larger element remains on the stack, it becomes the parent and the current node becomes its right child. Each element is pushed and popped once, resulting in linear O(n) time.
What is the best approach for Maximum Binary Tree?
The optimal approach uses a monotonic decreasing stack. It processes the array once and links nodes while maintaining decreasing order, ensuring each element is pushed and popped at most once. This builds the same tree as the recursive definition but runs in O(n) time with O(n) space.
Is Maximum Binary Tree asked at Google/Amazon/Meta?
Maximum Binary Tree is a classic stack and tree construction problem commonly seen in coding interviews at companies like Amazon and Google. It tests understanding of divide and conquer, monotonic stacks, and binary tree construction from arrays.
What data structure is used in Maximum Binary Tree?
The key data structures are binary trees and stacks. The recursive solution relies on divide and conquer to build the tree, while the optimized solution uses a monotonic stack to determine parent-child relationships efficiently.
What is the time complexity of Maximum Binary Tree?
The divide and conquer approach has O(n^2) worst-case time because each recursive call scans the remaining subarray to find the maximum. The optimized monotonic stack solution reduces this to O(n) time since each element is processed once. Space complexity for both approaches is O(n).
