区域和检索 树状数组
假设有一个长度为9的数组,数组中的值arr[i]
,前i个的总和sum[i]
如下。
某次,arr[3]
修改为0,那么有6个数据需要更新,查询前5个的总和为sum[4] = 11
某次,arr[0]
修改为3,那么有9个数据需要更新,查询前5个的总和为sum[4] = 13
有n个数,第i个修改了,那么sum(i)到sum(n-1)
需要更新,n为数组长度
树状数组
lowbit (x)= x & (-x)
取x在二进制中的最后一位1。
树状数组用C[i]
表示,值都初始化为0,下标从1开始。
arr[3] = 4
修改为0后,相当于减取4,管理到它的节点为树状数组中的C[4],管理到C[4]的下标为4 & (-4) = 8,为C[8]。管理到C[8]的下标为8 & (-8) = 16,超过长度,退出。只有C[4] ,C[8]需要减去4。
查询前5个数的总和为,首先是C[5]。5 - 5 & (-5) = 4,然后是C[4]。4 - 4 &(-4) = 0。因此只有C[5]和C[4],C[5]+C[4]=11。
arr[0] = 1
修改为3,相当于加2,管理到它的节点为树状数组中的C[1],管理到C[1]的下标为 1 + 1 & (-1) = 2,为C[2]。管理到C[2]的下标为2 + 2 & (-2) = 4,为C[4]。管理到C[4]的下标为4 + 4&(-4) = 8,为C[8]。管理到C[8]的下标为8 + 8 & (-8) = 16,超过长度,退出。因此,只有C[1]、C[2]、C[4]、C[8],需要加2。
查询前5个的总和,查询前7个数的和,只需要查询C[7]、C[6]、C[4]=26。只需要加3个。
给你一个数组 nums ,请你完成两类查询,其中一类查询要求更新数组下标对应的值,另一类查询要求返回数组中某个范围内元素的总和。
实现 NumArray 类:
NumArray(int[] nums) 用整数数组 nums 初始化对象
void update(int index, int val) 将 nums[index] 的值更新为 val
int sumRange(int left, int right) 返回子数组 nums[left, right] 的总和(即,nums[left] + nums[left + 1], …, nums[right])
示例:
输入:
[“NumArray”, “sumRange”, “update”, “sumRange”]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:
[null, 9, null, 8]
解释:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 9 ,sum([1,3,5]) = 9
numArray.update(1, 2); // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 8 ,sum([1,2,5]) = 8
#define lowbit(num) ((num)&(-num))
class NumArray {
private:
vector<int>tree;
vector<int>nums;
int size = 0;
void add(int index, int val) {
while (index <= size) {
tree[index] += val;
index += lowbit(index);
}
}
int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= lowbit(index);
}
return sum;
}
public:
NumArray(vector<int>& nums) {
this->size = nums.size();
this->nums = nums;
tree.resize(size + 1, 0);
for (int i = 0; i < size; i++) {
add(i + 1, nums[i]);
}
}
void update(int index, int val) {
int delta = val - nums[index];
nums[index] = val;
add(index + 1, delta);
}
int sumRange(int left, int right) {
return query(right + 1) - query(left);
}
};