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();
}
}
本文围绕Leetcode 654最大二叉树问题,介绍了Java实现的三种解法。普通递归类似构造树方法,时间复杂度O(n^2);ST表递归基于倍增思想,优化找最大值问题,时间复杂度O(n*logn);单调栈设置为递减栈,时间复杂度O(n),每个结点进栈出栈一次。
462

被折叠的 条评论
为什么被折叠?



