307. 区域和检索 - 数组可修改
引言
树状数组适用于什么场景?
- 答:单点修改、区间查询
lowbit 运算
lowbit()
运算:非负整数
n
n
n在二进制表示下 最低位 1 及其后面的 0 构成的数值
- 代码如下
int lowbit(int x) { return x & -x; }
树状数组概念
对于一个序列,我们在其上建立一个如下如所示的森林结构。
每个节点 t[x]
保存以
x
x
x (包括
x
x
x)为根的子树中叶节点值的和。
那么每个节点覆盖的长度是多少呢?
- 把每个节点
t[x]
的 x x x 转化为二进制后,我们会发现每一层的末尾的零的个数都是相同的;(如下图所示) - 而且零的个数与其覆盖的长度有关,即
t[x]
节点覆盖的长度就是lowbit(x)
此外,由下图可知,t[x]
节点的父节点为 t[x+lowbit(x)]
其中,
add
单点修改:
add(int x, int val)
以 add(3, 5)
为例,
在整颗树上维护这个值,需要一层一层向上找到父节点,并按照需要修改这个节点的值,即 + 5 +5 +5
代码如下:
void add(int x, int u) {
for (int i = x; i <= n; i += lowbit(i)) tree[i] += u;
}
一层都经过了处理即最坏复杂度为 O ( l o g n ) O(log n) O(logn)
query
前缀和查询:
int query(int x)
,查询节点 x x x 的前缀和。
查询这个点 x x x的前缀和,需要从这个点向左上找到上一个节点,并加上其节点的值。
可以发现向左上找上一个节点只需要将下标 -lowbit(x)
代码如下:
int query(int x) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i];
return ans;
}
如果需要求出 区间和,我们可以分别求出区间左、右端点的前缀和并相减
树状数组
思路:
- 本题便是“单点修改,区间求和”,直接套用如上 树状数组模板,即可
注意:树状数组是从 1 1 1 开始,而不是从 0 0 0 开始!!!
class NumArray {
int[] tree;
int lowbit(int x) {
return x & -x;
}
int query(int x) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += tree[i];
}
return res;
}
void add(int x, int val) {
for (int i = x; i <= n; i += lowbit(i)) {
tree[i] += val;
}
}
private int[] nums;
private int n;
public NumArray(int[] nums) {
this.nums = nums;
this.n = nums.length;
// 初始化树状数组(一定从1开始!)
tree = new int[n + 1];
for (int i = 0; i < n; i++) {
add(i + 1, nums[i]);
}
}
public void update(int index, int val) {
// 向上更新父节点
add(index + 1, val - nums[index]);
nums[index] = val;
}
public int sumRange(int left, int right) {
// 区间[left, right]元素之和,等于right+1前缀和 减去 left的前缀和
return query(right + 1) - query(left);
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* obj.update(index,val);
* int param_2 = obj.sumRange(left,right);
*/
参考: