LeetCode 327. 区间和的个数

一、题目

1、题目描述

  给你一个整数数组 nums以及两个整数 lowerupper。求数组中,值位于范围 [l, u](包含 lu)之内的 区间和的个数 。区间和 S(i, j)表示在 nums 中,位置从 ij的元素之和,包含 ij(i ≤ j)
  样例输入: nums = [-2,5,-1], lower = -2, upper = 2
  样例输出: 3

2、基础框架

  • C++ 版本给出的基础框架代码如下:
class Solution {
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {

    }
};

3、原题链接

LeetCode 327. 区间和的个数

二、解题报告

1、思路分析

   ( 1 ) (1) (1) 遇到任何区间和的问题,我们应该首先想到前缀和,首先计算数组的前缀和,这个问题中数据范围是 32位 整型,计算前缀和必然会溢出,所以可以用另一个 64 位的数组来存储,并且数组下标从 1 开始。
   ( 2 ) (2) (2) 对于任意一个 i i i,问题转变成了求满足以下不等式的 t ( 0 ≤ t < i ) t (0 \le t\lt i) t(0t<i) 的数量: l ≤ s u m [ i ] − s u m [ t ] ≤ u l \le sum[i] - sum[t] \le u lsum[i]sum[t]u
   ( 3 ) (3) (3) 既然如此,我们可以枚举 i i i,这样一来, s u m [ i ] sum[i] sum[i] 就变成了常量。虽然 s u m [ i ] sum[i] sum[i] 变成了常量,但是 s u m [ t ] sum[t] sum[t] 还是变量。于是对上述不等式进行一个变换: s u m [ i ] − u ≤ s u m [ t ] ≤ s u m [ i ] − l sum[i] - u \le sum[t] \le sum[i] - l sum[i]usum[t]sum[i]l
  这样一来,问题就转化成了求区间 [ s u m [ i ] − u , s u m [ i ] − l ] [sum[i] - u, sum[i] - l] [sum[i]u,sum[i]l] s u m [ t ] sum[t] sum[t] 的个数。
   ( 4 ) (4) (4) 假设我有一种容器,这种容器可以在 O ( l o g 2 n ) O(log_2n) O(log2n) 的时间复杂度内插入数据、可以在 O ( l o g 2 n ) O(log_2n) O(log2n) 的时间复杂度内统计前缀和,这样就能轻松满足上述条件了。满足这两个条件的数据结构有两种:树状数组线段树
   ( 5 ) (5) (5) 只要把接口抽象出来,具体是用树状数组实现,还是用线段树实现,在使用的人角度可以不用关心,但是有个前提,数据范围太大了,对于树状数组而言,数据范围应该在 [ 1 , x ] [1, x] [1,x] 之间,并且保持整数, x x x 一般在 1 0 6 10^6 106 以下;对于线段树而言,数据范围也是整数,并且保持左右端点的差值在 1 0 6 10^6 106 以下。
   ( 6 ) (6) (6) 但是,数据范围较大,所以需要进行离散化,将 大的数据 映射到 小的下标
   ( 7 ) (7) (7) 需要离散化的值主要有 s u m [ i ] 、 s u m [ i ] − l 、 s u m [ i ] − r sum[i]、sum[i]-l、sum[i]-r sum[i]sum[i]lsum[i]r

2、时间复杂度

   最坏时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

3、代码详解

class Solution {
    #define ll long long
    ll s[100010];
    vector <ll> bin;

    int getIndex(ll val) {
        int l = 0, r = bin.size() - 1;
        while(l <= r) {
            int mid = (l + r) >> 1;
            if(val > bin[mid]) {
                l = mid + 1;
            }else if(val < bin[mid]) {
                r = mid - 1;
            }else {
                return mid + 1;
            }
        }
        return -1;
    }
    

    int c[300010];
    int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int n) {
        while(x <= n) {
            c[x]++;
            x += lowbit(x);
        }
    }
    ll sum(int x) {
        ll s = 0;
        while(x) {
            s += c[x];
            x -= lowbit(x);
        }
        return s;
    }

public:
    int countRangeSum(vector<int>& nums, int l, int u) {
        int i, n = nums.size();
        int ans = 0;
        bin.clear();
        memset(c, 0, sizeof(c));
        s[0] = 0;
        for(i = 1; i <= n; ++i) {
            s[i] = s[i-1] + nums[i-1];
        }

        for(i = 0; i <= n; ++i) {
            bin.push_back( s[i] );
            bin.push_back( s[i] - u );
            bin.push_back( s[i] - l );
        }
        sort(bin.begin(), bin.end());
        bin.erase( unique(bin.begin(), bin.end()), bin.end() );

        add( getIndex( s[0] ), bin.size() );
        for(i = 1; i <= n; ++i) {
            ans += sum( getIndex(s[i] - l) ) - sum( getIndex(s[i] - u) - 1 );
            add( getIndex(s[i]), bin.size() );
        }
        return ans;
    }
};

三、本题小知识

  当数据范围较大,或者出现负数、或者出现小数,都可以采用离散化。


四、加群须知

  大致题集一览:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述



在这里插入图片描述


  为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
  不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」
  🔥联系作者,或者扫作者主页二维码加群,加入刷题行列吧🔥


🔥让天下没有难学的算法🔥

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

让你养成九天持续刷题的习惯
🔥《九日集训》🔥

入门级C语言真题汇总
🧡《C语言入门100例》🧡

组团学习,抱团生长
🌌《算法零基础100讲》🌌

几张动图学会一种数据结构
🌳《画解数据结构》🌳

竞赛选手金典图文教程
💜《夜深人静写算法》💜
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值