327. 区间和的个数

给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

说明:
最直观的算法复杂度是 O(n^2) ,请在此基础上优化你的算法。

示例:

输入: nums = [-2,5,-1], lower = -2, upper = 2,
输出: 3
解释: 3个区间分别是: [0,0], [2,2], [0,2],它们表示的和分别为: -2, -1, 2。

*****************      前缀和    *******************

区间和问题,一般可以联想到前缀和

对于数组nums[n],其前缀和为:

sum[i]=sum[i-1]+nums[i-1]

(sum[0]=0; sum[1]=nums[0]; sum[2]=nums[0]+nums[1])

解法1:    暴力法

对于每一个i,求区间[0,i-1],[1,i-1],……,[i-1,i-1]是否满足条件

class Solution {public:    int countRangeSum(vector<int>& nums, int lower, int upper) {        int len=nums.size();        vector<int> Sum(len+1,0);        int presum=0,cnt=0;        for(int i=1;i<=len;i++)        {            presum += nums[i-1];            for(int j=1;j<=i;j++)            {                if(presum-Sum[j-1]>=lower && presum-Sum[j-1]<=upper)                    cnt++;            }            Sum[i]=presum;        }        return cnt;    }};

时间复杂度:O(n^2)
空间复杂度:O(n),Sum数组用来存储前缀和。

解法2:  树状数组

用来解决动态前缀和问题,解决区间加/单点查询问题

查询复杂度为O(logN)

Lowbit函数:返回值是 入参转化为二进制后,最后一个1的位置所代表的数值。

C1, C3, C5, C7的下标的二进制表示 最后一位都是1,放在第一层;C2, C6的下标的二进制表示都为10,放在第二层。

子节点和其父节点的关系:

子节点下标X + lowbit(X) = 父节点下标 Y

举例:2+lowbit(2)=4;  3+lowbit(3)=4

修改单点的值,只需要更新覆盖其值的数组元素就行啦。举例,修改单点A2,则需要更新C2, C4, C8, C16的值(注意这里4是2+lobit(2), 8是4+lobit(4))。

故修改单点值的代码

void add(int x,int delta){    while(x<=n)//n为树状数组的长度    {        C[x]+=delta;        x+=lowbit(x);    }}

查询前缀和

举例,计算Sum13=A1+A2+……+A13

只需要计算C13+C12+C8

C13,  C12 , C8 可以理解为:13     .......   二进制表示为  1101

lowbit(13)=1, 13-lowbit(13)=12  .......   二进制表示为  1100

lowbit(12)=4, 12-lowbit(12)=8    .......   二进制表示为  1000

故查询前缀和的代码

int query(int x){    int res=0;    while(x)    {        res+=C[x];//C[x]为树状数组元素        x-=lowbit(x);    }    return res;}

树状数组的初始化:

public FeWickTree(int n){    this.len=n;    this.tree=new int[n+1];}public FeWickTree(int[] nums){    this.len=nums.length;    this.tree=new int[this.len+1];    for(int i=0;i<this.len;i++)    {        update(i,nums[i]);    }}void update(int i,int delta){    while(i<=this.len)    {        tree[i]+=delta;        i+=lowbit(i);    }}

此题树状数组题解:

typedef long long LL;class Solution {public:        vector data;    vector<int> tr;        // 查找小于等于x的第一个数的下标,找不到返回0    int binary_search1(LL x) {                int l = 0, r = data.size() - 1;        while (l < r) {            int mid = l + r + 1 >> 1;            if (data[mid] <= x) l = mid;            else r = mid - 1;        }        if (l == 0 && data[0] > x) return 0;        else return l + 1;    }        // 查找小于x的第一个数的下标,找不到返回0    int binary_search2(LL x) {        int l = 0, r = data.size() - 1;        while (l < r) {            int mid = l + r + 1 >> 1;            if (data[mid] < x) l = mid;            else r = mid - 1;        }        if (l == 0 && data[0] >= x) return 0;        else return l + 1;    }        int lowbit(int x) {        return x & -x;    }        int find(int x) {        int ans = 0;        for (int i = x; i; i -= lowbit(i)) ans += tr[i];        return ans;    }        void add(int x, int c) {        int n = tr.size() - 1;        for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;    }        int countRangeSum(vector<int>& nums, int lower, int upper) {        int n = nums.size();        LL sum = 0;        vector s(n);        for (int i = 0; i < n; i ++) {            sum += nums[i];            s[i] = sum;        }        data = s;        data.push_back(0);        sort(data.begin(), data.end());        data.erase(unique(data.begin(), data.end()), data.end());        tr = vector<int>(data.size() + 1);                int ans = 0;        int idx_zero = binary_search1(0);        add(idx_zero, 1);        for (auto x : s) {            LL a = x - upper, b = x - lower;            int idxa = binary_search2(a), idxb = binary_search1(b);            int idxx = binary_search1(x);            if (idxb != 0) {                if (idxa == 0) ans += find(idxb);                else ans += find(idxb) - find(idxa);            }            add(idxx, 1);           }        return ans;    }};

解法3:   归并排序

前置知识点:求逆序对

对于数组a,若对于ia[j]。则a[i]与a[j]构成逆序对。

求解逆序对个数经典的解法是归并排序。思路如下:

对于数组a[low,high),若 a[low,mid) 和 a[mid,high) 都已归并有序,则

对于左半区间 a[low,mid) 的每个元素a[left],计算 a[mid,high) 有多少个元素小于它。实现的代码模板如下:

//O(n),其中 n=hi-loint right=mid;for(int left=low;left{    //向右移动right指针,直到 a[right]>=a[left]    while(right!=high && a[left]>a[right])        right++;    //比a[left]小的元素有 right-mid 个    count += right-mid;}//因为左半区间和右半区间都已经归并,因此 left 越往右越大//故 right 无需每次都从 mid 开始

求解此题时,分两步:

1)求前缀和得到sums

2)计算前缀和数组中,当 i < j 时,sums[j]-sums[i]的值在[lower,higher]之间。

求解逆序对的个数是该题的一个特例,也就是计算nums[j]-nums[i]<0的个数。

class Solution {public:    void mergeResult(vector<int>& sums,int lo,int hi,int mid)//归并部分代码实现{        vector<int> tmp(hi-lo,0);        int k=0,left=lo,right=mid;        while(left        {            if(sums[left]                tmp[k++]=sums[left++];            else                tmp[k++]=sums[right++];        }        if(left==mid)        {            while(right                tmp[k++]=sums[right++];        }        if(right==hi)        {            while(left                tmp[k++]=sums[left++];        }        for(int i=lo,m=0;i            sums[i]=tmp[m++];    }    int merge_sort(vector<int>& sums,int lo,int hi,int lower,int upper){        if(hi-lo<=1) return 0;        int mid=(lo+hi)>>1;        int count=merge_sort(sums,lo,mid,lower,upper)+merge_sort(sums,mid,hi,lower,upper);        int right1=mid,right2=mid;        for(int left=lo;left        {            // 统计右侧-nums[left] < lower 的个数            while(right1!=hi && sums[right1]-sums[left]                right1++;            // 统计右侧-nums[left] <= upper 的个数            while(right2!=hi && sums[right2]-sums[left]<=upper)                right2++;              // 因此右侧-nums[left]的差在 [lower,upper] 的个数为:            //count += (right2 - mid) - (right1 - mid); 可以简写为下面这样:            count += right2-right1;                  }        //该函数可以C++函数替换(inplace_merge 原地归并)        //inplace_merge(nums.begin() + lo, nums.begin() + mid, nums.begin() + hi);        mergeResult(sums,lo,hi,mid);        return count;    }    int countRangeSum(vector<int>& nums, int lower, int upper) {        int len=nums.size();        vector<int> sums(len+1,0);        for(int i=1;i<=len;i++)        {           //计算前缀和            sums[i]=sums[i-1]+nums[i-1];        }        return merge_sort(sums,0,len+1,lower,upper);    }};

解法4:  前缀和+线段树

用线段树可将时间复杂度降至为O(NlogN)。题目要求lower<=sum(i, j)<= upper,sum(i, j)=prefixsum(j) - prefixsum(i-1),那么lower+prefixsum(i-1)<=prefixsum(j)<=upper+prefixsum(i-1)。所以利用前缀和将区间和转换成了前缀和在线段树中 query 的问题,只不过线段树中父节点中存的不是子节点的和,而是子节点出现的次数。另外,由于前缀和会很大,所以需要离散化。举例,prefixsum=[-3,-2,-1,0],用前缀和下标进行离散化,所以线段树中左右区间变成[0,3]。

注意:prefixsum 计算完后需要去重,去重后并排序,方便构造线段树。最后一步往线段树中倒叙插入 prefixsum 的时候,用的是非去重的。例如往线段树中插入prefixsum[5],query操作实际是做区间匹配,即lower<=sum(i, j)<=upper

举例:nums[6]=[-3, 1, 2, -2, 2, -1],lower=-3, upper=-1, prefixsum=[-3, -2, 0, -2, 0, -1],去重以后并排序得到sum=[-3,-2,-1,0]。离散化构造线段树,此处展示的是非离散化的线段树构造。

倒序插入prefixsum[5]=-1,

这时查找区间为[-3+prefixsum[5-1], -1+prefixsum[5-1]]=[-3,-1]。此时满足等式的只有 j=5,故此步res=1。

倒序插入prefixsum[4]=0,

这时查找区间为[-3+prefixsum[4-1], -1+prefixsum[4-1]]=[-5,-3]。此时没有满足等式的情况,故此步res=0。

倒序插入prefixsum[3]=-2,

这时查找区间为[-3+prefixsum[3-1], -1+prefixsum[3-1]]=[-3,-1]。此时满足等式的情况有 j=3 和 j=5,故此步res=2。

一次类推,一直计算到 插入prefixsum[0]的情况,最后的结果为各步res之和。

树状数组求逆序对_区间和的个数(树状数组)相关推荐

  1. 树状数组求逆序对_算法系列之-数组中的逆序对

    题目来源 剑指offer 01 题目描述 在数组中如果前一个数字大于后一个数字,则称为这个数字组合组成一个逆序对.输入一个数组,求所有的逆序对的总数. 如 数组 {7,5,6,4} 则它的逆序对是 ( ...

  2. 树状数组求逆序对_初识树状数组

    树状数组是用来解决数列多次单点修改和前缀和查询的利器. 首先我们来看问题的原型: 已知一个长度为n(n<=10 0000)的数列,初始值都是零,现在我们要对数列施加两种类型的操作共q(q< ...

  3. 牛客练习赛33 D tokitsukaze and Inverse Number (树状数组求逆序对,结论)

    链接:https://ac.nowcoder.com/acm/contest/308/D 来源:牛客网 tokitsukaze and Inverse Number 时间限制:C/C++ 1秒,其他语 ...

  4. Tido 习题-二叉树-树状数组求逆序对

    这里给大家提供一个全新的求逆序对的方法 是通过树状数组来实现的 题目描述   样例输入 Copy 5 2 3 1 5 4 样例输出 Copy 3 提示       #include<iostre ...

  5. [USACO17FEB] Why Did the Cow Cross the Road I P (树状数组求逆序对 易错题)

    题目大意:给你两个序列,可以序列进行若干次旋转操作(两个都可以转),对两个序列相同权值的地方连边,求最少的交点数 记录某个值在第一个序列的位置,再记录第二个序列中某个值 在第一个序列出现的位置 ,求逆 ...

  6. c语言数组求逆序对,LeetCode 面试题51. 数组中的逆序对

    面试题51. 数组中的逆序对 题目来源:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 题目 在数组中的两个数字,如果 ...

  7. Python(分治算法)问题 F: 求逆序对_给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

    问题 F: 求逆序对 题目描述 给定一个序列a1,a2,-,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目. 注意:n<=10^5,ai<=10^5 ...

  8. 【Luogu1908】逆序对(离散化,树状数组求逆序对)

    problem 给你一个长为n的序列A[] 求该序列的逆序对个数 solution 用b[i]保存下标i在A中出现的次数,那么数组b[i]在[l,r]上的区间和,就表示序列A在范围[l,r]内的有多少 ...

  9. 牛客 - What Goes Up Must Come Down(树状数组求逆序对)

    题目链接:点击查看 题目大意:给出一个长度为 n 的序列,每次操作可以交换相邻两个数字的位置,现在问最小进行多少次操作,可以使得序列的相对大小呈山峰状(中间高两边低,或非严格递增或非严格递减) 题目分 ...

最新文章

  1. 安装与使用 Trac 来管理项目
  2. 最好最坏和平均情况下的性能分析
  3. 基于linux环境采用update-alternatives 方式进行python版本切换
  4. jquery post php返回html,jquery ajax post 提交数据,返回的是当前网页的html?
  5. 天池在线编程 2020国庆八天乐 - 4. 生成更大的陆地(BFS)
  6. python附件发送到邮箱里_使用python将最新的测试报告以附件的形式发到指定邮箱...
  7. Codeforces Round #164 (Div. 2):B. Buttons
  8. 深度学习自学(二十六):人脸数据集
  9. 收集了一些容易出错的题,可能大家也不会注意到的基础知识(js)
  10. python中define的用法_C语言中#define用法总结
  11. Windows兼容性设置图文教程,Windows兼容模式怎么设置?
  12. 大屏项目屏幕分辨率适配
  13. wIN 7 一键清理垃圾
  14. 画出漂亮的神经网络图,神经网络可视化工具集锦
  15. WIN7开启远程桌面服务
  16. 计算机文化基础知识在未来工作中的应用论文,计算机文化基础论文
  17. MySql轻功-存储过程
  18. 笔记本的构造介绍--处理器
  19. 嵌入式linux下复位声卡驱动
  20. Docker系列---docker cgroup资源管理 | TLS通讯加密 | 详细讲解

热门文章

  1. FreeRTOS 之一 源码目录文件 及 移植详解
  2. mysql存储过程中文乱码_mysql存储过程碰到中文乱码问题
  3. c/c++程序员的技术栈
  4. deinstall 卸载grid_卸载Oracle 11g的Grid小计
  5. 官宣了!Apache ECharts 毕业成为 Apache 软件基金会顶级项目!
  6. decode判断不为空拼接_空指针4月内部赛官方WP
  7. php怎么克隆,利用php怎么对对象进行克隆
  8. idea 项目编译不成功-循环依赖的问题
  9. 部署testlink报错,安装wampserver时提示丢失MSVCR110.dll
  10. 17. Merge Two Binary Trees 融合二叉树