一、题目
1、题目描述
给你一个整数数组
nums
以及两个整数lower
和upper
。求数组中,值位于范围[l, u]
(包含l
和u
)之内的 区间和的个数 。区间和S(i, j)
表示在 nums 中,位置从i
到j
的元素之和,包含i
和j(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、原题链接
二、解题报告
1、思路分析
(
1
)
(1)
(1) 遇到任何区间和的问题,我们应该首先想到前缀和,首先计算数组的前缀和,这个问题中数据范围是 32位 整型,计算前缀和必然会溢出,所以可以用另一个 64 位的数组来存储,并且数组下标从 1 开始。
(
2
)
(2)
(2) 对于任意一个
i
i
i,问题转变成了求满足以下不等式的
t
(
0
≤
t
<
i
)
t (0 \le t\lt i)
t(0≤t<i) 的数量:
l
≤
s
u
m
[
i
]
−
s
u
m
[
t
]
≤
u
l \le sum[i] - sum[t] \le u
l≤sum[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]−u≤sum[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]−l、sum[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讲》🌌
几张动图学会一种数据结构 🌳《画解数据结构》🌳
竞赛选手金典图文教程 💜《夜深人静写算法》💜