在解类似 leetcode 307题区域和检索 - 数组可修改的题时,我们可以使用一种比较小众的数据结构,数组树。
数组树的结构依托于数组,它的结构看起来类似下面这种:
1,2,3。。。。代表他们在数组中的位置,在了解为什么有这种数据结构之前,请务必先看看上面提到的leetcode 307题。
在数组树中有两个主要的操作函数更新和查询。在更新数组中的某一个位置的数值的时候,其所有祖先节点全部都要更新,比如节点1更新,则2,4,8节点全部都要更新,更新的方式为当前节点的值加上子节点或自身更新后增加的值,比如现在节点1的值变为1,相对于原来的0值增加了1,则1,2,4,8的值都变为1;节点2的值变为4,相对于原来的0值增加了4,则2,4,8的值变为5。这种更新方式实际上是要在数组树中维护这样一个关系,节点值为sum(child.val) + self.val。
如果要更新数组树,那么必须找到更新的节点的所有祖先节点,计算方法为 i += lowbit(i);lowbit()的意思为数的二进制从最低位往高位计算,到第一个为1的位置,这之间的所有的二进制构成的数,比如1(b0001)的lowbit为(b0001),相加为2(b0010),2的lowbit为(b0010)相加为4(b0100)。lowbit的计算公式为lowbit(x) = x & (-x);以5为例,x = 5 = b0110 ,-x = ~x + 1 = b1010,lowbit(5) = 0010;
以上就是就是数组树的更新过程,如果要查询数组树,也就是获得前i个数组元素的和为多少时,毕竟这才是我们的目的,这时就需要用到另一种计算节点的方式。经过上面数组树更新方式的描述,我们可以很清楚的知道4存放了1,2,3,4的和,6存放了5,6的和,8存放了所有值的和,如果要计算前7个数的和,可想而知是要将数组树中4,6,7节点值相加。这实际上也有一个计算公式,i -= lowbit(i);它看起来就像下面这样。
这就是leetcode 307题所需要的数据结构,现在我们使用这个数据结构将这一题解决。在做题过程中一定要注意fenwicktree的下标是从1开始的。并且更新过程中传入的是相对于原来该元素增加的值。
class NumArray {
FenwickTree tree;
int[] nums;
public NumArray(int[] nums) {
tree = new FenwickTree(nums.length + 1);
this.nums = nums;
for(int i = 0; i < nums.length; i++){
tree.update(i + 1, nums[i]);
}
}
public void update(int i, int val) {
tree.update(i + 1, val - nums[i]);
nums[i] = val;
}
public int sumRange(int i, int j) {
return tree.query(j + 1) - tree.query(i);
}
}
class FenwickTree{
int[] sums;
public FenwickTree(int n){
sums = new int[n];
}
public void update(int i, int delta){
while(i < sums.length){
sums[i] += delta;
i += lowbit(i);
}
}
public int query(int i){
int sum = 0;
while(i > 0){
sum += sums[i];
i -= lowbit(i);
}
return sum;
}
private int lowbit(int i){
return i&-i;
}
}