Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
The update(i, val) function modifies nums by updating the element at index i to val.
Example:
Given nums = [1, 3, 5]sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
Note:
The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.
题意
- 给定一个数组,要求对数组进行两种操作
- 给定区间范围,求出该区间的和
- 更新数组中的一个值
思路
- 暴力,记录数组,查询什么区间和,立即O(n)扫描一遍计算出来,这样更新的话直接更新即可,操作可以直接O(1)完成
- 显然,对于更新频繁而查询极少的情况来说,速度的确非常快,但是,对于查询非常频繁的情况,这就会非常慢了(测试样例中应该包含大量的查询、更新操作),因此需要对查询进行优化
- 注意到,当查询非常频繁的时候,可能有大量重合的查询,如(1, 10),(2, 20),其中,中间(2, 10)部分可以说是经过了两次计算,这样的计算其实是可以优化的,而对于更新操作,也只需要计算修改位置的差值,然后更新区间和即可
- 当然,要是记住所有区间的和,也并不现实(这样更新操作复杂度太高)
- 那么,就有一种数据结构能够有效地维护区间和,称为Binary Index Tree,是线段树的一种简化形式
- 它的基本思想也是事先处理一部分区间和,避免重复查询计算
- 直观思路在于对于需要维护的区间和数组bit[N],利用下标即可标记数组和,如
00012,则该二进制数转为十进制为1,而二进制表示中,末尾有0个0,所以它表示为从下标1开始20=1个数的和,即bit[1] = numbers[1](PS: numbers下标从1开始)。又如10002,十进制表示为8,而二进制表示末尾有3个0,因此代表23=8个数字的和,即numbers[1]+…+numbers[8]。
- 综合来说,即对于十进制数
x<script type="math/tex; mode=display" id="MathJax-Element-1103">x</script>,其二进制表示末尾若有m个0,则bit[x] = numbers[x]+numbers[x-1]+..+numbers[x-2^m+1]
具体如图所示:
时间复杂度方面,对于m次操作,n个数的数组,初始化为O(nlogn),查询一次为O(logn),
- 对于更新操作,只需要跟原数组对比,对于所有有关那个数的区间和,全部更新一次即可,复杂度为o(logn)
- 所以总的复杂度为O(max(m, n)logn)
- PS:这是定长数组的一般算法,对于不定长数组,即包括增删数字,那分两种情况,一种只在末尾增删,那一样可以处理,因为可以把原数组末尾的数都设为0,增删只是加减操作而已,另一种情况,对于增删中间的数的话,则一般不能使用BIT了
- 代码如下
#define N 1000000+10
int ___vec[N];
int bit[N];
int n;
class NumArray {
public:
NumArray(vector<int> &nums) {
n = nums.size();
memset(bit,0,sizeof(bit));
memset(___vec,0,sizeof(___vec));
for (int i=0;i<n;i++)
{
update(i,nums[i]);
}
}
void update(int i, int x) {
i++;
int y=x;
x -= ___vec[i];
___vec[i] = y;
while (i<=n)
{
bit[i] += x;
i += i&-i;
}
}
int sum(int i)
{
int s=0;
while (i>0)
{
s += bit[i];
i -= i&-i;
}
return s;
}
int sumRange(int i, int j) {
j++;
if (i==0) return sum(j);
else return sum(j)-sum(i);
}
};