题目:
样例:
思路:
这道题采用的是:线段树,一种二叉搜索树,把一个区间划分为一些单元区域,每个单元区域对应线段树中国的一个叶子节点。
然而,我没有使用过线段树,故在此进行详细的介绍和记录。
概念
完全二叉树:设二叉树的深度为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));
}
}
}