【LeetCode】307 . 区域和检索 - 数组可修改

307 . 区域和检索 - 数组可修改



区间和解题思路

  • 这是一道很经典的题目,通常还能拓展出一大类问题。

    针对不同的题目,我们有不同的方案可以选择(假设我们有一个数组):

    • 数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
    • 多次修改某个数(单点),求区间和:「树状数组」、「线段树」
    • 多次修改某个区间,输出最终结果:「差分
    • 多次修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
    • 多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
  • 这样看来,「线段树」能解决的问题是最多的,那我们是不是无论什么情况都写「线段树」呢?

    答案并不是,而且恰好相反,只有在遇到第 4 类问题,不得不写「线段树」的时候,我们才考虑线段树。因为「线段树」代码很长,而且常数很大,实际表现不算很好。我们只有在不得不用的时候才考虑「线段树」

  • 总结一下,我们应该按这样的优先级进行考虑:

    • 简单求区间和,用「前缀和」
    • 多次将某个区间变成同一个数,用「线段树」
    • 其他情况,用「树状数组」

方法:树状数组

树状数组概述

  • 要介绍树状数组,首先引入二叉树:

  • 变形一下,就得到了树状数组

定义树状数组

  1. 首先定义一个累加和数组 sums,假设数组有 8 个元素,如图所示,其中 ni ​是原始数据, si 是累加和数据 :

    cpp 复制代码
    s[1]=n[1];
    s[2]=n[1]+n[2];
    s[3]=n[3];
    s[4]=n[1]+n[2]+n[3]+n[4];
    s[5]=n[5];
    s[6]=n[5]+n[6];
    s[7]=n[7];
    s[8]=n[1]+n[2]+n[3]+n[4]+n[5]+n[6]+n[7]+n[8];
  2. 那么我们如何初始化 sums 这个数组呢?

    当我们要对 sums[1] 初始化时,也就是加上 nums[1],而 sums[2]、sums[4]、sums[8] 也都需要加上 nums[1]

    将这几个节点的下标写成二进制:sums[(001)]、sums[(010)]、sums[(100)]、sums[(1000)]

    不难发现,sums[(1000)] = sums[(100)] + 4sums[(100)] = sums[(010)] + 2sums[(010)] = sums[(001)] + 1,即可以表示成 sums[(y)] = sums[(x)] + C,C 就是 x 最低位数 1 代表的二进制,比如 x = 100 = 4 。

    对于上述发现,我们可以使用函数 lowbit(x) 来计算 x 最低位1 所代表的二进制:

    cpp 复制代码
    int lowbit(int x) {
        return x & (-x);
    }

    因此,sums[(y)] = sums[(x)] + lowbit(x)

    当我们对 sums 数组进行初始化的时候,我们就是将所有和 nums[i] 相关联的节点都加上 nums[i],不断向上操作,直到下标越界。

    cpp 复制代码
    void insert(int index, int val) {
        // 下标+1
        int x = index + 1;
        while (x < sums.size()) {
            sums[x] += val;
            x += lowbit(x); 
        }
    }

更新树状数组

  • 更新操作和初始化类似,都是执行向上操作,使用 x += lowbit(x)来寻找被影响的数组下标。

    cpp 复制代码
    void update(int index, int val) {
        int x = index + 1;
        while(x < sums.size()) {
            // 减去原先的值,加上新值
            sums[x] = sums[x] - nums[index] + val;
            x += lowbit(x);
        }
        nums[index] = val;
    }

查询树状数组

  • 查询是一个向下访问的过程,使用 x -= lowbit(x)来寻找下一个下标。

    cpp 复制代码
    int query(int x) {
        int ans = 0;
        while(x > 0) {
            ans += sums[x];
            x -= lowbit(x);
        }
        return ans;
    }

区间求和

  • 在完成左右端点的前缀和查询后,就可以对区间求和。

    cpp 复制代码
    int sumRange(int left, int right) {
        return query(right + 1) - query(left);
    }

代码

cpp 复制代码
class NumArray {
public:
    vector<int> sums; // 累加和
    vector<int> nums;
    int lowbit(int x){
        return x & (-x);
    }
    // 原数组长度+1, 原因是计算lowbit时,使用下标0会进入死循环
    NumArray(vector<int>& nums) : nums(nums){
        sums.resize(nums.size() + 1);
        for(int i=0; i<nums.size(); ++i){
            // 初始化累加和数组
            update(i, nums[i]);
        }
    }
    void insert(int index, int val) {
        // 下标+1
        int x = index + 1;
        while (x < sums.size()) {
            sums[x] += val;
            x += lowbit(x); 
        }
    }
    void update(int index, int val) {
        int x = index + 1;
        while(x < sums.size()) {
            // 减去原先的值,加上新值
            sums[x] = sums[x] - nums[index] + val;
            x += lowbit(x);
        }
        nums[index] = val;
    }
    
    int sumRange(int left, int right) {
        return query(right + 1) - query(left);
    }
    int query(int x) {
        int ans = 0;
        while(x > 0) {
            ans += sums[x];
            x -= lowbit(x);
        }
        return ans;
    }
};

/**
 * 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);
 */

参考资料

  1. 树状数组详解

  2. [树状数组] 详解树状数组, 包含更新查询图解, 秒懂lowbit含义(JAVA 65ms, 68.6MB)

  3. 关于各类「区间和」问题如何选择解决方案(含模板)

相关推荐
LiLiYuan.39 分钟前
【Java 6种线程状态】
java·开发语言
foundbug9991 小时前
基于混合整数规划的电池容量优化 - MATLAB实现
数据结构·算法·matlab
itzixiao1 小时前
L1-047 装睡 (5分)[java][python]
java·开发语言·python
用户298698530141 小时前
不用无头浏览器,Java 如何将 HTML 转成图片?
java·后端
Chengbei112 小时前
红队专属Bing Dork自动化工具,敏感信息侦察效率拉满、自动生成可视化信息泄露审计报告
java·人工智能·安全·web安全·网络安全·自动化·系统安全
敖正炀2 小时前
集合-Set深入解析
java
下次再写2 小时前
Java互联网大厂面试技术问答实战:涵盖Java SE、Spring Boot、微服务及多场景应用
java·数据库·缓存·面试·springboot·microservices·技术问答
公众号-老炮说Java2 小时前
IDEA 2026.1 + Claude Code = 降维打击
java·ide·intellij-idea
memcpy03 小时前
LeetCode 2452. 距离字典两次编辑以内的单词【暴力;字典树】中等
算法·leetcode·职场和发展
千寻girling3 小时前
RabbitMQ 详细教程(38K字数)
java·后端·面试