Leetcode 654.最大二叉树 Maximum Binary Tree(Java)
##Tree##
最大二叉树
普通递归
与普通的构造树方法类似
- 对于一段序列
[l,l+1,...r-1,r]
,找到序列中的最大元素max
和其位置pos
,则该序列生成树的根节点的值应该为max
- 该序列生成树的根节点的左子树应该在子序列
[l,l+1,...pos-1,pos]
中生成,因此递归处理该子序列,将根节点的左子树指向递归生成的树节点 - 同理,对于右子树,该序列生成树的根节点的右子树应该在子序列
[pos+1,pos+2,...r-1,r]
中生成,因此递归处理该子序列,将根节点的右子树指向递归生成的树节点 - 注意递归出口
l > r
- 序列中,找最大值的处理方法为顺序查找
时间复杂度: O(n^2)
在序列中找最大值的方法为顺序查找O(n),共有n个结点
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums, 0, nums.length - 1);
}
public TreeNode build(int[] n, int l, int r) {
if (l > r) return null;
int index = -1, max = Integer.MIN_VALUE;
for (int i = l; i <= r; i ++) {
if (n[i] > max) {
max = n[i];
index = i;
}
}
TreeNode res = new TreeNode(max);
TreeNode left = build(n, l, index - 1);
TreeNode right = build(n, index + 1, r);
res.left = left;
res.right = right;
return res;
}
}
ST表递归
普通递归的做法瓶颈是在任意给定的序列中找最大值
因此可以考虑优化该问题,使用ST表优化
ST表基于倍增思想,可以做到O(n*logn)预处理,O(1)的查询
时间复杂度: O(n*logn)
class Solution {
int[][] f;
int n, k;
public TreeNode constructMaximumBinaryTree(int[] nums) {
n = nums.length;
k = (int)(Math.log(n) / Math.log(2));
f = new int[n][k + 1];
for (int j = 0; j <= k; j ++) {
for (int i = 0; i + (1 << j) - 1 < n; i ++) {
if (j == 0) f[i][j] = i;
else {
int l = f[i][j - 1], r = f[i + (1 << (j - 1))][j - 1];
f[i][j] = nums[l] > nums[r] ? l : r;
}
}
}
return build(nums, 0, n - 1);
}
public int query(int[] n, int l, int r) {
int len = r - l + 1;
int k = (int)(Math.log(len) / Math.log(2));
int a = f[l][k], b = f[r - (1 << k) + 1][k];
return n[a] > n[b] ? a : b;
}
public TreeNode build(int[] n, int l, int r) {
if (l > r) return null;
int temp = query(n, l, r);
TreeNode res = new TreeNode(n[temp]);
res.left = build(n, l, temp - 1);
res.right = build(n, temp + 1, r);
return res;
}
}
单调栈
考虑到越大的结点,越是要在后面处理,因此单调栈设置为递减栈,本次处理中,用LinkedList
实现栈,但进行的操作绝大部分和普通栈没有区别
- 遍历所有结点
curr
,如果遍历结点比栈顶结点小curr.val < stack.peekLast().val
,则将该结点直接进栈,该结点curr
一定是栈顶结点stack.peek()
的右子树部分 - 遍历结点比栈顶元素大
curr.val > stack.peekLast().val
,栈顶结点stack.peekLast()
一定是该结点curr
的左子树部分- 栈顶结点出栈
temp = stack.pollLast()
,这时需要判断curr
是否是新的栈顶元素stack.peekLast()
的右儿子 - 判断正在遍历结点
curr
和新的栈顶结点stack.peekLast()
的大小,栈顶结点小于正在遍历的结点sta.peekLast().val < curr.val
,栈顶元素sta.peekLast()
一定是curr
的左子树部分,且栈顶元素的右儿子一定是temp
- 上述步骤循环至栈顶结点大于正在遍历的结点
- 栈顶结点大于正在遍历的结点
sta.peekLast() > curr.val
,则curr
的左儿子应该是temp
- 栈顶结点出栈
curr
结点进栈- 遍历完所有结点后,栈中仍有几棵独立的子树,根据单调栈的性质,栈顶元素比栈底元素小,且晚于栈底元素进栈,因此栈顶结点应该是栈底结点的右儿子
时间复杂度: O(n)
每个结点都进栈出栈一次
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
LinkedList<TreeNode> sta = new LinkedList<>();
TreeNode curr = null;
for (int i = 0; i < nums.length; i ++) {
curr = new TreeNode(nums[i]);
while (!sta.isEmpty() && sta.peekLast().val < nums[i]) {
TreeNode temp = sta.pollLast();
if (!sta.isEmpty() && sta.peekLast().val < nums[i]) {
sta.peekLast().right = temp;
} else {
curr.left = temp;
}
}
sta.offerLast(curr);
}
while (!sta.isEmpty()) {
curr = sta.pollLast();
if (!sta.isEmpty()) {
sta.peekLast().right = curr;
}
}
return curr;
}
}
单调栈
用LinkedList
实现栈
- 遍历所有结点
curr
,若栈顶结点的值小于当前遍历结点的值stack.peekLast().val < curr.val
,栈顶结点stack.peekLast()
应该为curr
的左儿子结点,curr.left = stack.pollLast()
循环该操作至栈顶结点的值大于当前遍历结点的值 - 栈顶结点的值大于当前遍历结点的值
stack.peekLast() > curr.val
,且栈不为空时,表示栈顶元素的右儿子暂时应该是curr
- 将当前遍历结点
curr
进栈 - 最后取栈底元素为生成树的根节点,这里可以采用
LinkedList
的pollFirst()
操作,这是所有操作中唯一不符合栈操作的操作
时间复杂度: O(n)
每个结点只进栈和出栈一次
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
Deque<TreeNode> stack = new LinkedList<>();
for(int i = 0; i < nums.length; i++) {
TreeNode curr = new TreeNode(nums[i]);
while(!stack.isEmpty() && stack.peek().val < nums[i]) {
curr.left = stack.pop();
}
if(!stack.isEmpty()) {
stack.peek().right = curr;
}
stack.push(curr);
}
return stack.isEmpty() ? null : stack.removeLast();
}
}