第一章 栈和队列
1.8 构造数组的 MaxTree
【题目】
定义二叉树节点如下:
public class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
一个数组的 MaxTree 定义如下:
- 数组必须没有重读元素;
- MaxTree 是一棵二叉树,数组的每一个值对应一个二叉树节点;
- 包括 MaxTree 树在内且在其中的每一棵子树上,值最大的节点都是树的头。
给定一个没有重复元素的数组 arr,写出生成这个数组的 MaxTree 的函数,要求如果数组长度为 N,则时间复杂度为 O(N),额外空间复杂度为 O(N)。
【难度】
校 ★★★☆
【题解】
以下列原则来建立 MaxTree 树:
- 每一个数的父节点是它左边第一个比它大的数和它右边第一个比它的数中,较小的那个;
- 如果一个数左边没有比它的数,右边也没有,即这个数是整个数组的最大值,那么这个数是 MaxTree 的头节点。
证明如下:
- 通过这个方法,所有的数能生成一棵树,这棵树可能不是二叉树,但肯定是一棵树,而不是多棵树(森林)。在数组中的所有数都不同,而一个较小的数肯定会以一个比自己大的数作为父节点,那么最终所有的数向上找都会找到数组中的最大值,所以它们会有一个共同的节点。
- 通过这个方法,所有的数最多都只有两个孩子,即这棵树可以用二叉树表示,而不需要多叉树。假设 a 这个数在单独一侧有两个孩子,不妨设在右侧。假设两个孩子一个是 k1,另一个是 k2,即有 ···a···k1···k2···。因为 a 是 k1 和 k2 的父亲,所以 a>k1,a>k2.根据题意,k1 和 k2 不相等,所以 k1 和 k2 可以分出大小。先假设 k1 < k2,那么 k1 可能会以 k2 为父节点,而绝不会以 a 为父节点。因为每一个数的父节点是它左边第一个比它大的数和它右边第一个比它大的数中较小的那个,而 a>k2。再假设 k1 > k2。那么 k2 可能会以 k1 为父节点,绝不会以 a 为父节点。因为 k1 才可能是是 k2 左边第一个比它大的数。总之,k1 和 k2 肯定有一个不是 a 的孩子。由此可证明任何一个数的单独一侧,其孩子数量都不可能超过 1 个,最多只有 1 个,那么任何一个数最多偶两个孩子。
利用栈可以尽可能快地找到每一个数左右两边第一个比它大的数。找一个数左边第一个比它大的数,从左到右遍历每个数,栈中保持递减序列新来的数不停地利用 Pop 出栈顶,直到栈顶比新数大或没有数。同样的方法可以求得每个数右边第一个比它大的数。
【实现】
- MaxTree.java
import java.util.HashMap;
import java.util.Stack;
public class MaxTree {
private static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
private int[] arr;
private Node maxTree;
public MaxTree() {
}
public MaxTree(int[] arr) {
this.arr = arr;
}
public void setArr(int[] arr) {
this.arr = arr;
build();
}
public Node getMaxTree() {
if (this.maxTree == null) {
build();
}
return this.maxTree;
}
private void build() {
if (this.arr == null || this.arr.length == 0) {
return;
}
Node[] nodes = new Node[this.arr.length];
for (int i = 0; i < this.arr.length; ++i) {
nodes[i] = new Node(this.arr[i]);
}
Stack<Node> stack = new Stack<>();
HashMap<Node, Node> lBigMap = new HashMap<>();
HashMap<Node, Node> rBigMap = new HashMap<>();
for (int i = 0; i < nodes.length; ++i) {
Node cur = nodes[i];
while ((!stack.isEmpty()) && stack.peek().value < cur.value) {
popStackSetMap(stack, lBigMap);
}
stack.push(cur);
}
while (!stack.isEmpty()) {
popStackSetMap(stack, lBigMap);
}
for (int i = nodes.length - 1; i >= 0; --i) {
Node cur = nodes[i];
while ((!stack.isEmpty()) && stack.peek().value < cur.value) {
popStackSetMap(stack, rBigMap);
}
stack.push(cur);
}
while (!stack.isEmpty()) {
popStackSetMap(stack, rBigMap);
}
Node head = null;
for (int i = 0; i < nodes.length; ++i) {
Node cur = nodes[i];
Node left = lBigMap.get(cur);
Node right = rBigMap.get(cur);
if (left == null && right == null) {
this.maxTree = cur;
} else if (left == null) {
if (right.left == null) {
right.left = cur;
} else {
right.right = cur;
}
} else if (right == null) {
if (left.left == null) {
left.left = cur;
} else {
left.right = cur;
}
} else {
Node parent = left.value < right.value ? left : right;
if (parent.left == null) {
parent.left = cur;
} else {
parent.right = cur;
}
}
}
}
private void popStackSetMap(Stack<Node> stack, HashMap<Node, Node> map) {
Node pop = stack.pop();
if (stack.isEmpty()) {
map.put(pop, null);
} else {
map.put(pop, stack.peek());
}
}
}
- MaxTreeTest.java
public class MaxTreeTest {
public static void main(String[] args) {
int[] arr = {3, 4, 5, 1, 2};
MaxTree maxTree = new MaxTree(arr);
maxTree.getMaxTree();
}
}