前言--------
距离上一次写博客,已经一周了,博主可是没有偷懒,,不过也算是偷懒吧,积累了多篇(大概有5篇)博文,果然什么事情都是积不得的,今天恰逢中秋,祝大家中秋快乐Q^Q(不过今天刚发表,看到的人肯定没有几个 ,,QAQ)
题目:
定义二叉树的节点如下:
public class Node{
public int value;
public Node left;
public Node right;
public Node(int date){
this.value = data;
}
}
一个数组的MaxTree定义如下:
1.数组必须没有重复元素。
2.MaxTree是一颗二叉树,数组的每一个值对应一个二叉树节点。
3.包括MaxTree树在内且在其中的每一颗子树上,值最大的节点都是树的头。
给定一个没有重复元素的数组arr,写出生成这个数组的MaxTree 的函数,要求如果数组的长度为N,则时间赋值度为O(N)。
分析:
哎哎哎哎哎,这道题确实是太难了,我思考不出来。。。。。。但是还好看了解法,自己能够实现出来。。
题目给出的做法是:
设一个数为x,把min(x左边第一个比它大的数,x右边第一个比他大的数)作为一个数的父亲节点。如果一个数左右两边都不存在比它大的数,说明这个数就是最大值,也就是头结点。按这种规则来建树,就能建成MaxTree。当然了直接给出 一个解法之后,还必须给出证明:
证明这个做法能够得到一颗MaxTree 等价于 证明这个做法得到的是一颗二叉树。
首先证明:这个做法得到的确实是一颗树,不是森林,既不会是多颗树。
这个还是比较好理解的。这个做法下,除了根节点以外,每个节点都有一个比自己大的树,每个节点都能通过自己的父亲节点,来不断往上走,最终每个点都会到达根节点(也就是该数组的最大值),也就是这样每个节点都会有一个共同的头节点,证明完毕。
接着证明:这个做法下,得到的树是一颗二叉树。
要证明是二叉树等价于证明:对应数组中的任何一个数,其单独一侧,孩子的数量都不可能超过1个即可。
我们先假设a这个数在单独一侧有2个孩子,不防设在右侧。假设这两个孩子一个是k1,另一个是k2,即 ---a----k1----k2.
然后我们只需要从这个假设出发,如果得到与这个假设相矛盾的结论,就可以证明这个假设是错了。这个假设又是结论的对立面,它错了就说明我们的结论是对的。
(下面这段证明来自左神的书:ORZ,要是让自己证明的话,自己实在是不会证明:)
因为a是k1和k2的父,所以a>kl, a>k2。根据题意,k1和k2不相等,所以kl和k2可以分出大小,先假设k1是较小的,k2 是较大的:
那么k1可能会以k2为父节点,而绝对不会以a为父节点,因为根据我们的方法,每一个数的父节点是它左边第一个比它大的数和它右边第个比它大的数中较小的那个,又有a>k2。
再假设k2是较小的,k1是较大的:
那么k2可能会以kI为父节点,也绝对不会以a为父节点,因为根据我们的方法,kl才可能是k2左边第个遇到的比k2大的数,而绝对不会轮到a。
总之,k1 和k2肯定有一个不是a的孩子,
所以,任何一个数的单独一侧,其孩了数量都不可能超过I个,最多只会有1个。进而我们知道,任何一一个数最多会有2个孩子,而不会有更多。
证明完毕。
代码实现分析:
我们最主要的就是要快速找到一个数左边第一个比它的数和右边第一个比它大的数。看到这里是不是很兴奋了呢,用单调栈就可以在O(n)的复杂度快速找到,这么好的办法就可以摆脱暴力的O(n^2)的做法了。单调递减栈的精髓:一个数入栈时,说明遇到了左边第一个比它大的树或者左边没有数比它大;一个数出栈时,说明遇到了右边第一个大于它的数。哈哈哈,是不是有点兴奋,其实就是让你用单调栈模拟题意,其实就是一个简单模拟题了:
我的做法是用一个Left[i],来记录第i个元素左边第一个比它大的元素的下标,同理,right[i]就是用来记录右边第一个比它大的元素的下标。left[i]或right[i] =-1,表示 左边或者右边不存在比它大的数。
附Java代码(经过牛客Oj测试,是正确的)
package code_180;
import java.util.*;
public class MaxTree {
private int []res ; //存储每个节点的父亲节点
private int []left; //存储左边第一个大于它的数的下标
private int []right; //存储右边第一个大于它的数的下标
public Node [] allNode ; //开辟一个节点数组
public Node head; //树的头结点
public int [] buildMaxTree(int []arr, int n) {
res = new int[arr.length];
left = new int [arr.length];
right = new int [ arr.length];
allNode = new Node[arr.length];
Stack<Integer>stack = new Stack<Integer>();
for(int i = 0 ;i < arr.length; i++) {
if(allNode[i] == null)
System.out.println("NULL");
else
System.out.println("No NULL");
}
//单调递减栈预处理出left和right数组
for(int i = 0 ;i < arr.length; i++) {
allNode[i] = new Node(arr[i]);
while(!stack.isEmpty() && arr[ stack.peek() ]< arr[i]) {
right[ stack.pop() ] = i;
}
if(!stack.empty())
left[i] = stack.peek();
else
left[i] = -1;
stack.push(i);
}
while(!stack.isEmpty()) { //此时栈中剩余的元素,右边都没有存在比它大的数
right[stack.pop()] = -1;
}
//开始建树
head = null;
for(int i = 0 ;i < arr.length;i++) {
if(right[i]==-1 && left[i]==-1) {
res[i] = -1;
head = allNode[i];
}
else if(right[i] != -1 && left[i]!= -1) {
res[i] = ( arr[left[i]] < arr[right[i]] ? left[i]: right[i]);
if(allNode[res[i]].left == null)
allNode[res[i]].left = allNode[i];
else
allNode[res[i]].right = allNode[i];
}
else if(right[i] == -1) {
res[i] = left[i];
if(allNode[res[i]].left == null)
allNode[res[i]].left = allNode[i];
else
allNode[res[i]].right = allNode[i];
}
else if(left[i] == -1) {
res[i] = right[i];
if(allNode[res[i]].left == null)
allNode[res[i]].left = allNode[i];
else
allNode[res[i]].right = allNode[i];
}
}
return res;
}
public void inOrderTraversal(Node Head) {
if(Head != null) {
inOrderTraversal(Head.left);
System.out.println(Head.value + " ");
inOrderTraversal(Head.right);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MaxTree myTree =new MaxTree();
int [] arr = {3,1,4,2};
int [] ans =myTree.buildMaxTree(arr, arr.length);
for(int i = 0 ;i <ans.length;i++) {
System.out.println(ans[i]);
}
System.out.println("-----------");
myTree.inOrderTraversal(myTree.head);
}
}
//树的节点定义
class Node{
int value;
Node left;
Node right;
Node(int v){
value = v;
left = right = null;
}
}