Java-约翰的生意

题目:

这里写图片描述

样例:

这里写图片描述

思路:

这道题采用的是:线段树,一种二叉搜索树,把一个区间划分为一些单元区域,每个单元区域对应线段树中国的一个叶子节点。
然而,我没有使用过线段树,故在此进行详细的介绍和记录。

概念

完全二叉树:设二叉树的深度为H,除第h层外,其他各层(1~h-1)的结点数都达到最大个数,且第h层的所有结点都连续集中在最左边。例如:
完全二叉树和非完全二叉树
辨析:满二叉树 – 国外:树上的任意节点,要么度为0,要么度为2。即要么是叶子节点,要么同时具有左右字数;国内:层数为K,则结点数为(2^K)-1,从外形上看,满二叉树一定是一个三角形。
对于二叉树,一般采用链表结构存储,对于完全二叉树,更加适合用数组来存储,这样便于理解。对于数组存储线段树,需要开辟四倍的空间,以防止访问越界。推导如下:
对于一颗完全二叉树,如果它有n个叶子节点,在最好的情况下,它是一颗满二叉树,那么它的树的高度为log2n+1; 对于树不满的情况下,其高度为:⌈ Log2n ⌉ + 1,该值小于等于log2n+1+1。又,对于一颗二叉树,它的总结点数最多(满二叉树时最多)为:等比数列求和,其中a1=1,q=2(等比数列求和公式),x = 树高 = ⌈ Log2n ⌉ + 1 <= log2n+1+1,代入公式计算得到总结点数最多为4n-1,即开辟四倍的空间大小是安全的。
(可能会问,对于n个叶子节点的完全二叉树,其非叶子节点数目一定为n-1,那么有效空间为2n-1,这样是不是就是够了?其实不然,实际上我们需要的空间是满二叉树的空间,在没有有效结点的位置其实也占据了一定的数组空间,故而实际需要的还是4n的空间大小。)
简单记忆:足够的空间=数组大小n的四倍
实际上足够的空间= (n向上扩展到最近的2的某个次方数)的两倍-1;
例如,n=5,5扩充到8(2的三次方),线段树需要:8*2-1=16-1=15的位置。

创建线段树

//A:用来构建线段树的数组,start:数组的起始位置,end:数组的结束位置
private SegmentTreeNode build(int A[],int start,int end){
    SegmentTreeNode node = new SegmentTreeNode(start,end,A[0]);
    //给每个节点加一个属性sum,用来表示当前节点的范围的数值总和
    if(node.start == node.end){ //只有一个节点时
        node.sum = A[start];
        return node;
    }
    int mid = (start + end)/2;
    node.left = build(A,start,mid); //递归的构造左子树
    node.right = build(A,mid+1,end);  //递归的构造右子树
    node.sum = node.left.sum + node.right.sum; //根据左右树根节点的值更新树根节点的值。
    return node;
}
Q:线段树的结构?时间复杂度?空间复杂度?

假设,给定一个区间【L,R】,只要L

Q:为什么出现了线段树?这样有什么好处?
Q:想一想,为什么这道题适合使用线段树?还有哪些地方适合使用线段树?

解答:

public class Solution {
    /**
     * @param A: The prices [i]
     * @param k: 
     * @return: The ans array
     */
    public int[] business(int[] A, int k) {
        SegmentTreeNode node  = build(A,0,A.length-1);

        int a[] = new int[A.length];
        for(int i=0;i<a.length;i++){
            SegmentTreeNode n = node;
            int min = Math.max(i-k,0);
            a[i]=Math.max(A[i]-query(node,min,max),0);
        }
        return a ;
    }

        //定义线段树
        class SegmentTreeNode{
            public int start =0;
            public int end = 0;
            public int sum = 0;
            public SegmentTreeNode left = null;
            public SegmentTreeNode right = null;

            public SegmentTreeNode(int start,int end,int sum){
                this.start = start;
                this.end = end;
                this.sum = sum;
            }
        }

        //构造线段树
        //A:用来构建线段树的数组,start:数组的起始位置,end:数组的结束位置
        private SegmentTreeNode build(int A[],int start,int end){
            SegmentTreeNode node = new SegmentTreeNode(start,end,A[0]);
            //给每个节点加一个属性sum,用来表示当前节点的范围的数值总和
            if(node.start == node.end){ //只有一个节点时,这个节点即是全部的范围
                node.sum = A[start];
                return node;
            }
            int mid = (node.start + node.end)/2; //中间位置
            node.left = build(A,start,mid); //递归的构造左子树
            node.right = build(A,mid+1,end);  //递归的构造右子树
            node.sum = node.left.sum + node.right.sum; //根据左右树根节点的值更新树根节点的值。
            return node;
        }

        //查询线段树
        private int query(SegmentTreeNode node, int start,int end){
            if(start>end){
                return 0 ;
            }
            if(node.start == start && node.end == end){
                return node.sum;
            }
            int mid = (node.start+node.end)/2;
            if(end<=mid){
                return query(node.right,start,end);
            }
            else if(start>mid){
                return query(node.right,start,end);
            }
            else{
                //求最小值
                return Math.min((query.left,start,mid),query(node.right,mid+1,end));
            }
        }


}

猜你喜欢

转载自blog.csdn.net/autumn_wating1977/article/details/79466983