一、原理
简介:
「树状数组」是一种可以动态维护序列前缀和的数据结构,它的功能是:
单点更新 update(i, v): 把序列 i 位置的数加上一个值 v
区间查询 query(i): 查询序列 [1⋯i] 区间的区间和,即 i 位置的前缀和
修改和查询的时间代价都是 O(logn),其中 nn 为需要维护前缀和的序列的长度。
结构:
上图中黑色数组是原来的数组,用A代替,红色数组是我们的树状数组,用C代替,由图可知
- C[1] = A[1];
- C[2] = A[1] + A[2];
- C[3] = A[3];
- C[4] = A[1] + A[2] + A[3] + A[4];
- C[5] = A[5];
- C[6] = A[5] + A[6];
- C[7] = A[7];
- C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可得出以下规律:
树状数组的每一项C[i]:
原数组的前缀和:
前辈的智慧2^k=i&(-i);//i为树状数组索引
主要代码:
//长度
int n;
//对应原数组和树状数组
int a[105],c[105];
//根据索引计算2^k
int lowbit(int x){
return x&(-x);
}
//在i位置加上k
void updata(int i,int k){
while(i <= n){
c[i] += k;
i += lowbit(i);
}
}
//求A[1 - i]的和
int getsum(int i){
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
二、应用
思路:维护树状数组C,用于倒序遍历原数组时记录小于当前元素值的元素个数。
tick:离散化,首先对题目中的数组nums去重,然后排序,得到数组a,数状数组根据a维护
class Solution {
private int[] c;
private int[] a;
public List<Integer> countSmaller(int[] nums) {
List<Integer> resultList = new ArrayList<Integer>();
discretization(nums);
init(nums.length + 5);
for (int i = nums.length - 1; i >= 0; --i) {
int id = getId(nums[i]);
resultList.add(query(id - 1));
update(id);
}
Collections.reverse(resultList);
return resultList;
}
private void init(int length) {
c = new int[length];
Arrays.fill(c, 0);
}
private int lowBit(int x) {
return x & (-x);
}
private void update(int pos) {
while (pos < c.length) {
c[pos] += 1;
pos += lowBit(pos);
}
}
private int query(int pos) {
int ret = 0;
while (pos > 0) {
ret += c[pos];
pos -= lowBit(pos);
}
return ret;
}
private void discretization(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
for (int num : nums) {
set.add(num);
}
int size = set.size();
a = new int[size];
int index = 0;
for (int num : set) {
a[index++] = num;
}
Arrays.sort(a);
}
private int getId(int x) {
return Arrays.binarySearch(a, x) + 1;
}
}