Given an arraay nums, construct a maximum binary tree where:
- The root is the maximum number.
- The
leftsubtree is construcuted from the elements left to the max, that isnums[0..i-1]. - The
rightsubtree is construcuted from the elements right to the max, that isnums[i+1..n-1].
nums = [3,2,1,6,0,5]
// Construct the tree:
6
/ \
[3,2,1] [0,5]fun constructMaximumBinaryTree(nums: IntArray): TreeNode? {
return construct(nums, 0, nums.size - 1)
}
private fun construct(nums: IntArray, start: Int, end: Int): TreeNode? {
if (start > end) return null
var max = nums[start]
var maxIndex = start
for (i in (start + 1)..end) {
if (max < nums[i]) {
max = nums[i]
maxIndex = i
}
}
val root = TreeNode(max)
root.left = construct(nums, start, maxIndex - 1)
root.right = construct(nums, maxIndex + 1, end)
return root
}- Time Complexity:
O(n^2), for eachconstructcall, we need to iterate through the array to find the maximum value, which takesO(n)time. Then we recursively callconstructfor the left and right subtrees, which takesO(n)time in worst case. Total time complexity isO(n^2). - Space Complexity:
O(n), the recursion stack takesO(n)space.
Worst case:
[1, 2, 3]
// Skewed tree
3
/
2
/
1We have to scan
[1, 2, 3]:ntimes[1, 2]:n-1times[1]:n-2times
Total n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 times.
We can use a monotonic decreasing stack to construct the tree:
- Stack always keeps all possible
leftchildren of the max node. - Pop the nodes are smaller, and become left child of current node when we find a larger node. (
stack.last() < num) - If the stack is not empty, the current node is the right child of the previous node in stack.
fun constructMaximumBinaryTree(nums: IntArray): TreeNode? {
val stack = ArrayDeque<TreeNode>()
for (num in nums) {
val node = TreeNode(num)
while (stack.isNotEmpty() && stack.last().`val` < num) {
// We will keep assigning left child of the larger node to the smaller node in stack
node.left = stack.removeLast()
}
// The current node is the right child of previous node in stack
if (stack.isNotEmpty()) {
stack.last().right = node
}
stack.addLast(node)
}
return stack.first()
}- Time Complexity:
O(n), we iterate through the array once, and for each element, we only push it onto the stack once and pop it at most once. - Space Complexity:
O(n), the stack takesO(n)space.
There is a very nice visualization of the process.
For nums = [3,2,1,6,0]:
// Iterate 2:
nums = [3, 2, 1, 6, 0]
i
stack = [3]
stack.last().right <- 2
tree =
3
\
2
stack = [3, 2]
// Iterate 1:
nums = [3, 2, 1, 6, 0]
i
stack = [3, 2]
stack.last().right <- 1
tree =
3
\
2
\
1
stack = [3, 2, 1]
// --------------------------------
// Iterate 6:
nums = [3, 2, 1, 6, 0]
i
stack = [3, 2, 1] < 6
// execute while loop
6.left <- 1
tree =
3
\
2 6
\ /
1
stack = [3, 2] < 6
// execute while loop
6.left <- 2
tree =
3 6
\ /
2
\
1
stack = [3] < 6
// execute while loop
6.left = 3
tree =
6
/
3
\
2
\
1
stack = [6]
// --------------------------------
// Iterate 0:
nums = [3, 2, 1, 6, 0]
i
stack = [6]
stack.last().right <- 0
tree =
6
/ \
3 0
\
2
\
1
stack = [6, 0]
// --------------------------------
// End of iteration:
return stack.first() = 6