树状数组
1.什么是树状数组?
顾名思义,就是用数组来模拟树形结构呗。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。和Trie树的构造方式有类似之处。
2.树状数组可以解决什么问题
可以解决大部分基于区间上的更新以及求和问题。
3.树状数组和线段树的区别在哪里
树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟。
4.树状数组的优点和缺点
修改和查询的复杂度都是O(logN),而且相比线段树系数要少很多,比传统数组要快,而且容易写。
缺点是遇到复杂的区间问题还是不能解决,功能还是有限。
树状数组基本模板
int n;
int a[1005],c[1005]; //对应原数组和树状数组
int lowbit(int x){//得到最低位0的位置
return x&(-x);
}
void updata(int i,int k){ //在i位置加上k
while(i <= n){
c[i] += k;
i += lowbit(i);
}
}
int getsum(int i){ //求A[1 - i]的和
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
区间更新时涉及到差分数组的运用
假设我们规定A[0] = 0;
则有 A[i] = Σij = 1D[j];(D[j] = A[j] - A[j-1]),即前面i项的差值和,这个有什么用呢?例如对于下面这个数组
- A[] = 1 2 3 5 6 9
- D[] = 1 1 1 2 1 3
如果我们把[2,5]区间内值加上2,则变成了
- A[] = 1 4 5 7 8 9
- D[] = 1 3 1 2 1 1
当某个区间[x,y]值改变了,区间内的差值是不变的,只有D[x]和D[y+1]的值发生改变
树状数组求逆序数:
class Solution {
private int[] c;
public List<Integer> countSmaller(int[] nums) {
List<Integer> v = new ArrayList<Integer>();
List<Integer> resultList = new ArrayList<Integer>();
c = new int[nums.length+5];
Arrays.fill(c, 0);
//去重
for(int i=0;i<nums.length;i++){
if (!v.contains(nums[i])) {
v.add(nums[i]);
}
}
// int[] m=new int[nums.length];
//离散化,先排序,然后二分查找出对应元素的等效值
Collections.sort(v);
int[]m=new int[v.size()];
for(int i=0;i<v.size();i++){
m[i]=v.get(i);
}
//从后往前遍历整个序列,将出现过的元素在树状数组中记录+1,添加之前,比当前元素小的前缀和就是当前位置的逆序数
for(int i=nums.length-1;i>=0;i--){
int id = Arrays.binarySearch(m, nums[i]) + 1;
resultList.add(getsum(id - 1));
update(id,1);
}
Collections.reverse(resultList);
return resultList;
}
public int lowbit(int x){
return x&(-x);
}
//在i的位置加上k
public void update(int i,int k){
while(i < c.length){
c[i] += k;
i += lowbit(i);
}
}
//获取从1到i的和
public int getsum(int i){
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
}