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) 如何区分,继续重复该操作即可。
image

✨如何更新
< ! 树状数组中的下标都是从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);
    }

};

posted @ 2026-03-19 18:58  hhhueu  阅读(1)  评论(0)    收藏  举报