1.思考
已知存在长度为n的数组,批量修改指定元素,每次增加val,如何快速求解指定范围内[n,m]区间元素的和。
a.暴力
单次修改:O(1)
单次求和:O(n)
n次修改:O(n)
n次求和:O(n^2)
b.合并
(1)如果将元素两两合并进行存储时
单次修改:2
单次求和:n/2
n次修改:2n
n次求和:(n^2)/2
如果四个合并一次,八个合并一次那。。。。
2.树状数组
a.前置知识lowbit(x)
b.推导
根据b.合并中合并多次的时是否可以移除重复项,使用其他已有的结果进行组合求值那?
规律总结:
上面时树状数组的结构图,t[x]保存以x为根的子数中叶子节点值的和,原数组为a[]
那么原数组前4项的和t[4]=t[2]+t[3]+a[4]=t[1]+a[2]+t[3]+a[4]=a[1]+a[2]+a[3]+a[4],我们通过观察节点的二进制数,进一步发现,树状数组中节点x的父节点为x+lowbit(x),例如t[2]的父节点为t[4]=t[2+lowbit(2)]
c.场景
1.单点修改,单点查询,
2.区间修改,单点查询,
3.区间查询,区间修改
d.模板
public class NumArray {
int[] nums;
int[] tree;
int n;
public NumArray(int[] nums) {
n = nums.length;
this.nums = nums;
tree = new int[n + 1];
for(int i = 0; i < n; i++){
add(i + 1, nums[i]);
}
}
//单点新增
void add(int x, int u) {
for (int i = x; i <= n; i += lowBit(i)) {
tree[i] += u;
}
}
//单点求和
int query(int x) {
int ans = 0;
for (int i = x; i > 0; i -= lowBit(i)) {
ans += tree[i];
}
return ans;
}
//单点更新
public void update(int i, int val) {
add(i, val - nums[i-1]);
nums[i-1] = val;
}
//区间求和
public int sumRange(int l, int r) {
if (r >n){
return 0;
}
return query(r) - query(l-1);
}
int lowBit(int x) {
return x & -x;
}
public static void main(String[] args) {
int[] nums = {1,2,3,4,5,6,7,8,9,10};
NumArray numArray = new NumArray(nums);
System.out.println(numArray.sumRange(9,10));
}
}