Binary Indexed Tree 树状数组
做Leetcode 做到MeetingRoomII的时候我知道不用线段树或者树状数组是不太好搞了。还是来学习一下吧。
树状数组算是线段树的一种特殊情况(子集),所以树状数组能解决的问题线段树一定能做,但线段树能做的树状数组不一定能做。
引入的问题
问题1
对一个数组进行如下操作
update(i1,i2,operation)
求 value(i)=?
例如:int[] ar=new int[N]
update(ar,2,5,+,4) //(2 to 5).map(ar(_)+=4)
return ar(k)
问题2
对一个数组进行如下操作 update(index,operation,value)
求 sum(i1,i2)=?
例如:int[] ar=new int[N]
update(ar,3,+,5); //ar[3]+=5
update(ar,11,-,3); //ar[11]-=3
。。。。。。
求:sum(ar,0,12)? //(0 to 12).map(ar(_)).sum
应用线段树的思路,对问题1,可以在中间节点缓存操作O(lgn),求解时再计算出结果。
对问题2,可以直接缓存sum,每次操作时都对sum附带进行操作O(lgn),而求结果时,直接使用缓存的sum结果O(lgn)。
树状数组的解法
按照刚才的解题分析,应当使用线段树进行操作,但线段树操作效率比较低,是否一种折中的办法呢?
例如:把线段树的每个节点映射到一个额外的数组上?
那么问题很明确,如何将一颗有N个叶子节点的树映射到一个长度为N的数组上?
最直观的思路显然是这样:
图看上去很容易理解,我们希望将中间结果(12)(58)等存在另外一个数组B[]里,剩下的问题只有一个,怎么把这些节点向数组的index映射?而且这个映射显然是算法可描述的,这样在计算时才能容易找到各个节点。
究竟怎样想出来的这种映射方式已经不得而知,但的确是个很神妙的构想,这也是Binary Index Tree的精髓了。
基本设计
图2中黄色的块被废弃了,如果用a->b表示a存入b则:
(1~2)->2
(1~4)->4
(5~6)->6
(1~8)->8
1->1
3->3
5->5
7->7
是不是有点规律了?
- 奇数点全部存原数组值
- 偶书点K存入的位数与K&(-K)后面的0相关,由M个0就存了1<<M个数字
求解问题2
如果希望计算(st,ed)的sum时,如何计算呢?直接计算st到ed之间的数据相当难算,但是
sum(st,ed)=sum(1,ed)-sum(1,st-1)
这时候再看一下图2是不是明白了?
从新定义一个函数sumFromStart(k)表示从1加到K的和。
sum(st,ed)=sumFromStart(ed)-sumFromStart(st-1)
最后看看这个sumFromStart写法吧其实很容易想到:
- 2 是10计算1~2的和
- 4 是100计算了1~4的和
- 8 是1000计算了1~8的和
如果我们想算1~7
7=111=100+10+1
而且17=(14)+(5~6)+7
所以sumFromStart(7)=B(4)+B(6)+B(7)
再写清楚点,如果是二进制:
sumFromStart(111)=B(100)+B(110)+B(111)
想明白了? 还没有?那就看图吧
所以,sumFromStart(k)定义如下
int sumFromStart(int k,int[] b){
int sum=0;
while(k!=0){
sum+=b[k];
k=k-(k&(-k));
}
}
最后还有一个更新操作,因为是单个更新,所以注意要把上面的点也更新了,以对A[5],操作为例,需要更新B(5),B(6),B(8)。写出来这三个
B(101)
B(110)
B(1000)
看不出啥太明显的规律啊?
101 +1 =110
110 +10=1000
明白了么? k+k&(-k)啊,所以update写出来
void update(int index,int v,Operation=add,int[] b){
while(index<b.length){
b[index]+=v;
index=index+index&(~index);
}
}
//问题2返回
void sum(int st,int ed,int[]b){
return sumFromStart(ed,b)-sumFromStart(st-1,b);
}
求解问题1
问题1是段累加,单点求值,所以可以把段累加过程加入B数组(复杂度lgn),求解时再算单点(复杂度lgn)
int[] b=new int[N];
void update(int st,int ed,int added,){
updateFromStart(st-1,-added,b);
updateFromStart(ed,added,b);
}
void updateFromStart(int index,int added,int[] b){
while(index<b.length){
b[index]+=added;
index-=index&(-index);
}
}
int getV(int index){
int sum=0;
while(index<b.length){
sum+=b[index];
index+=index&(-index);
}
return sum;
}
求解MeetingRoomII
最后给出meeting RoomII代码,很遗憾,这个题最后求最大重叠,所以遍历了每个点找最大,时间复杂度O(nlgn)
public int minMeetingRooms(Interval[] ins) {
int st=Integer.MAX_VALUE;
int ed=Integer.MIN_VALUE;
for(Interval in: ins){
st=Math.min(st,in.start);
ed=Math.max(ed,in.end);
}
int[] bis=new int[ed-st+5];
int delta=st-1;
for(Interval in:ins){
add(in.start-delta,-1,bis);
add(in.end-delta,1,bis);
}
int max=0;
for(int i=1;i<bis.length;i++){
max=Math.max(max,getFrom(bis,i));
}
return max;
}
private int getFrom(int[] bis, int i) {
int res=0;
while(i<bis.length){
res+=bis[i];
i+=i&(-i);
}
return res;
}
private void add(int index,int added,int[] b){
while(index>0){
b[index]+=added;
index-=index&(-index);
}
}