LeetCode 307 | 区域和检索
题目:
给你一个数组 nums ,请你完成两类查询。
其中一类查询要求 更新 数组 nums 下标对应的值
另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 ,其中 left <= right
实现 NumArray 类:
- NumArray(int[] nums) 用整数数组 nums 初始化对象
- void update(int index, int val) 将 nums[index] 的值 更新 为 val
- int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和
示例 1:
输入:
["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); // 返回 1 + 3 + 5 = 9
numArray.update(1, 2); // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8
常规算法:
1、求和时暴力累加,虽然更新操作的时间复杂度只需要O(1),但是求和的时间复杂度是O(n)。
2、 前缀和 虽然求和的时间复杂度是O(1),但在更新操作需要O(n)时间复杂度。
树状数组:可以中和二者的复杂度
主要思想是将前缀和的 [1, i] 分割为几个小组,如果不分割则是常规算法2;如果分割的太细则成为常规算法1。
✨如何拆分?
-如果把一个正整数 i 拆分成若干个不同的 2 的幂(从大到小),那么只会拆分出 O(logi) 个数。
比如: 13 = 8 + 4 + 1 ,那么可以将 [1, 13] 区间拆分为 [1, 8]、[9, 12]、[13, 13]三个区间。具体的划分操作为:将下标 index 先拆分出最小的2的幂,即index的二进制表示中最右边的1所代表的数字,记作lowbit(index)(例如13拆分出1);得到区间[index - lowbit(index) + 1, index],问题就转换为 index - lowbit(index) 如何区分,继续重复该操作即可。

✨如何更新
< ! 树状数组中的下标都是从1开始,为了方便转移下标 >
例如下标 i = 3 需要更新数字时,区间[3, 3]、[1, 4]、[1,8]、[1, 16]都需要修改,这些区间的和是保存在数组tree的右端点下标中的,需要修改的下标为3、4、8、16...
可以观察到规律:转移下标时,增加的刚好是上述得到的lowbit(index)
我们可以通过求i & -i得到 lowbit,因为 -i 的补码是二进制原码取反后+1,取并后刚好会得到最右边的1所代表的数字。
void add(int index, int val){
for(int i = index; i <= n; i += i & -i){
tree[i] += val;
}
}
void update(int index, int val) {
int dt = val - nums[index];
add(index + 1, dt);
nums[index] = val;
}
✨如何初始化
初始化函数可以通过 对于每个下标 index 引用更新函数完成。所以重点是如何实现更新。
但是LeetCode该题需要注意的是:前面的update函数并没有引入原数组nums,所以需要在NumArray类里自定义并初始化一个数组nums存放数字。
int n;
vector <int> tree;
vector <int> &nums;
NumArray(vector<int>& nums) : nums(nums){
n = nums.size();
tree.resize(n + 1, 0);
for(int i = 0; i < n; i++){
add(i + 1, nums[i]);
}
}
代码中的nums(nums)目的就是初始化引用成员 nums,让它绑定到传入的参数。
✨如何求和?
例如在求 [1, 13] 区间和时,下标 i = 13,tree[13]存放着 [13, 13] 的和;再将 i 转移到12,通过i -= i & -i的方式。tree[12]存放[9, 12]的区间和;再继续转移i得到所有区间和。
int Sum(int x){
int res = 0;
for(int i = x + 1; i > 0; i -= i & -i){
res += tree[i];
}
return res;
}
🔑总代码
class NumArray {
public:
int n;
vector <int> tree;
vector <int> &nums;
void add(int index, int val){
for(int i = index; i <= n; i += i & -i){
tree[i] += val;
}
}
int Sum(int x){
int res = 0;
for(int i = x + 1; i > 0; i -= i & -i){
res += tree[i];
}
return res;
}
NumArray(vector<int>& nums) : nums(nums){
n = nums.size();
tree.resize(n + 1, 0);
for(int i = 0; i < n; i++){
add(i + 1, nums[i]);
}
}
void update(int index, int val) {
int dt = val - nums[index];
add(index + 1, dt);
nums[index] = val;
}
int sumRange(int left, int right) {
return Sum(right) - Sum(left - 1);
}
};

浙公网安备 33010602011771号