数据结构——树状数组(区间更新、求和问题)
假设你正在读取一串整数。每隔一段时间,你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作,也就是说:
实现 track(int x) 方法,每读入一个数字都会调用该方法;
实现 getRankOfNumber(int x) 方法,返回小于或等于 x 的值的个数。
leetcode
解题思路:这是一道树状数组地模版题。
什么是树状数组呢?
就是将数组元素信息整合在一些关键节点上,这样,需要获取某些信息的时候,就不需要去遍历每一个节点,只需要去检索这些关键的节点,就能把时间复杂度降到O(logn)。
如何构建树状数组
设C即为红色节点,每个红色节点的值就是其子节点的总和。树状数组中,节点值的表达式为
C [ i ] = A [ i − 2 k + 1 ] + A [ i − 2 k + 2 ] + . . . + A [ i ] ; C[i] = A[i - 2^k+1] + A[i - 2^k+2] + ... + A[i]; C[i]=A[i−2k+1]+A[i−2k+2]+...+A[i];
k为i的二进制中从最低位到高位连续零的长度。
其中,如何求2的k次方呢?这个方法叫做lowbit 利用计算机补码和位与的特性:x & -x;
求和问题
这里需要快速求某个黑色节点一下的和。通过树状数组的思想。这里的遍历步长就是lowbit。
在添加节点的时候,就遍历他的父节点,把他们的值都加一。
在获取值的时候,累加小于等于其值的关键节点。
为什么每次从x + 1开始枚举呢?
0位不用啊,因为0的lowbit还是0,这样就进入死循环了。
class StreamRank {
int[] nums;
int n = 50010;
public StreamRank() {
nums = new int[50010];
}
public void track(int x) {
for(int i = x + 1; i <= n; i += lowbit(i)) {
nums[i] += 1;
}
}
public int getRankOfNumber(int x) {
int res = 0;
for(int i = x + 1; i != 0; i -= lowbit(i)) {
res += nums[i];
}
return res;
}
int lowbit(int x) {
// 获取x的最低位值 奇数就是1 偶数就是能整除这个数的最大的2的幂 0就为0
return x & -x;
}
}