一、题目
1、题目描述
给你一个整数数组 nums
以及两个整数 lower
和 upper
。求数组中,值位于范围 [lower, upper]
(包含 lower
和 upper
)之内的 区间和的个数 。
区间和 S(i, j)
表示在 nums
中,位置从 i
到 j
的元素之和,包含 i
和 j
(i
≤ j
)。
示例1:
输入:nums = [-2,5,-1], lower = -2, upper = 2
输出:3
解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。
示例2:
输入:nums = [0], lower = 0, upper = 0
输出:1
提示:
- 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
- − 2 31 < = n u m s [ i ] < = 2 31 − 1 -2^{31} <= nums[i] <= 2^{31} - 1 −231<=nums[i]<=231−1
- − 1 0 5 < = l o w e r < = u p p e r < = 1 0 5 -10^5 <= lower <= upper <= 10^5 −105<=lower<=upper<=105
- 题目数据保证答案是一个 32 位 的整数
2、基础框架
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
}
};
3、原题链接
327. 区间和的个数
二、解题报告
1、思路分析
(1)如果暴力解决,时间复杂度为
O
(
N
3
)
O(N^3)
O(N3),因为子数组的数量为
O
(
N
2
)
O(N^2)
O(N2),而遍历验证这些子数组的和的时间复杂度为
O
(
N
)
O(N)
O(N),所以总的时间复杂度为
O
(
N
3
)
O(N^3)
O(N3)
(2)优化1:利用前缀和数组,则不用遍历验证子数组的和,时间复杂度就变成
O
(
N
2
)
O(N^2)
O(N2)
(3)优化2:任何一个子数组都会以某个位置结尾(常见思维模型),如果以
i
i
i 位置结尾的子数组达标的个数为
a
a
a 个,以
j
j
j 位置结尾的子数组达标的个数为
b
b
b 个,那么最终结果为每个位置结尾的子数组达标的个数之和(即先求以每个位置结尾的子数组和的达标个数)
(4)如果
s
u
m
(
i
.
.
.
j
)
sum(i...j)
sum(i...j) 累加和在 [lower, upper] 范围上,那么
s
u
m
(
0...
j
)
−
s
u
m
(
0...
i
−
1
)
sum(0...j) - sum(0...i - 1)
sum(0...j)−sum(0...i−1) 也在这个范围上。
举例:数组 arr(0…17),要求的范围为 [10, 40],那么如何求以 17 这个位置为结尾的子数组和落在 [10,40] 这个范围上的个数。
假设 arr(0…17) 的前缀和为 100,本质上就是在求必须以0开头有多少个前缀和落在 [100 - upper, 100 - lower] = [60, 90] 这个范围上,再一经转化就能得到以 17 位置为结尾的子数组。再具体点,sum(0…17) = 100,而sum(0…5) = 10,则可以反推出 sum(6…17) 的累加和 = 100 - 10 = 90,也就是说 arr[6] ~ arr[17] 这个子数组是达标的。
总结来说就是假设 0 ~ i i i 整体累加和是 x x x,题目的目标是 [ l o w e r , u p p e r ] [lower, upper] [lower,upper],思路转化为求必须以 i i i 位置结尾的子数组累加和有多少个落在 [ l o w e r , u p p e r ] [lower, upper] [lower,upper] 范围上 等同于 必须从0出发有多少个前缀和在 [ x − u p p e r , x − l o w e r ] [x - upper, x - lower] [x−upper,x−lower] 上
原始数组 nums
处理成前缀和数组 sum
,求 sum
数组中的每个数 x
之前有多少个数落在 [x - upper, x - lower]
范围上,怎么求呢?
假设数组为 [3, -2, 4, 3, 6, -1],而目标范围是[1,5]。
准备一个结构,存放前缀和,一开始初始化时没有前缀和,可以在该结构中放一个0。
首先来到 0 位置,值为3,该位置为了达标,等同于要找它之前在结构中有几个数在[3 - 5, 3 - 1] = [-2, 2] 范围,在存放前缀和的结构中发现只有1个0,于是以0位置结尾的子数组达标个数为1,然后将当前的前缀和 sum = 3 放入到结构中。结构目前的值[0,3];
接着来到 1 位置,当前前缀和sum = 3 + (-2) = 1,等同于在问结构中有多少个数在[1 - 5, 1 - 1] = [-4, 0] 范围,发现只有0,于是1位置达标的子数组个数为1,然后将当前的前缀和 1 加入到结构中。结构目前的值 [0,3,1];
接着来到 2 位置,当前前缀和 sum = 1 + 4 = 5,等同于在问结构中有多少个数在 [5 - 5, 5 - 1] = [0, 4] 范围,发现有3个(0,3,1),所以以2位置结尾的子数组达标个数是3,然后将当前的前缀和 5 放入结构中。结构目前的值 [0,3,1,5];
以此类推。
那么这个结构应该具备什么样的性质呢?
-
首先,这个结构能进行插入操作,此处插入的是整数;
-
其次,该结构能提供一种查询:有多少个数字落在给定的 [a,b] 范围上;
-
最后,该结构还必须能存放重复的数字。
假设某种结构中存储的数字全部按序组织且可以接受重复的数字,要支持从[a,b]的值的查询并不需要直接提供这种功能,只需要支持一种查询:小于某个key的数量。举例:要求[5,10]范围上的数字个数,先求小于5的数字个数 c 1 c1 c1,再求小于11的数字个数 c 2 c2 c2,则 c 2 − c 1 c2 - c1 c2−c1 就是 [5,10] 范围上的数字个数。
所以该问题实际就变成了这个结构要实现3个功能:
(1)可以存储重复数字;
(2)可以添加数字;
(3)可以查询小于key的数字个数。
这里就要提到一个问题:有序表不能接收重复的数字,在有序表上每个key都是不同的。那如果要使用有序表,该怎么办呢?
这个问题很好解决——将多个数字压在一起即可。增加一个数据项记录每个结点经过的数字个数,用压在一起的方式生成一棵树。
例如给定的数字是[5, 5, 3, 3, 3, 6, 6],则依次形成的树为:
如何统计 5 的个数呢?在最后生成的树上,左树3个数,右树2个数,所以 5 的个数就是
7
−
(
3
+
2
)
=
2
7 - (3 +2) = 2
7−(3+2)=2 个。
这样的树就能支持查询小于key的数的个数,比如如下的树要查询 < 46 的值的个数:(往左不累加,往右才累加)
系统的有序表之所以没有做这个功能,是因为并不知道根据实际情况需要用到什么辅助数据项,如果不管应用场景,添加的若干数据项就会非常冗余。
至此,梳理下本题的脉络:要求的是整个数组中有多少个子数组的累加和落在 [ a , b ] [a,b] [a,b] 范围上,只要是子数组问题,满足某个S标准(此处就是累加和在 [ a , b ] [a,b] [a,b] 范围),通常的思路就是必须以 i i i 位置结尾的情况下有多少个子数组达标,进而做了一个转化,假设 0~ i i i 的累加和是 S S S,要想求子数组必须以 i i i 结尾的情况下,有几个子数组的累加和在 [ a , b ] [a,b] [a,b] 范围上,其实质就是在求 i i i 之前有多少个前缀和在 [ S − b , S − a ] [S-b, S-a] [S−b,S−a] 这个范围上。
进而想到了需要一个黑盒,这个黑盒可以放置所有的前缀和,且不去重,来到 i i i 位置的时候(假设到 i i i 位置的前缀和为S),如果之前的前缀和都在这结构中,且能查出在该结构中落在 [ S − b , S − a ] [S-b, S-a] [S−b,S−a] 范围的数量,对每个位置查询出来的结果相加就是全局的答案。
2、时间复杂度
O ( N l o g N ) O(N logN) O(NlogN)
3、代码详解
C++版
class Solution {
public:
class SBTNode {
public:
long key;
SBTNode *lchild;
SBTNode *rchild;
long size;
long cnt;
SBTNode(long k) : key(k), size(1), cnt(1), lchild(nullptr), rchild(nullptr) {}
};
class SizeBalancedTreeSet {
private:
SBTNode *root;
unordered_set<long> hashSet;
//右旋
SBTNode *rightRotate(SBTNode *cur) {
//cur的key重复的个数
long same = cur->cnt - (cur->lchild ? cur->lchild->cnt : 0) - (cur->rchild ? cur->rchild->cnt : 0);
//右旋
SBTNode *leftNode = cur->lchild;
cur->lchild = leftNode->rchild;
leftNode->rchild = cur;
//更新size
leftNode->size = cur->size;
cur->size = (cur->lchild ? cur->lchild->size : 0) + (cur->rchild ? cur->rchild->size : 0) + 1;
//更新cnt
leftNode->cnt = cur->cnt;
cur->cnt = (cur->lchild ? cur->lchild->cnt : 0) + (cur->rchild ? cur->rchild->cnt : 0) + same;
return leftNode;
}
//左旋
SBTNode *leftRotate(SBTNode *cur) {
long same = cur->cnt - (cur->lchild ? cur->lchild->cnt : 0) - (cur->rchild ? cur->rchild->cnt : 0);
//左旋
SBTNode *rightNode = cur->rchild;
cur->rchild = rightNode->lchild;
rightNode->lchild = cur;
//更新size
rightNode->size = cur->size;
cur->size = (cur->lchild ? cur->lchild->size : 0) + (cur->rchild ? cur->rchild->size : 0) + 1;
//更新cnt
rightNode->cnt = cur->cnt;
cur->cnt = (cur->lchild ? cur->lchild->cnt : 0) + (cur->rchild ? cur->rchild->cnt : 0) + same;
return rightNode;
}
SBTNode *maintain(SBTNode *cur) {
if (cur == nullptr) return nullptr;
long leftSize = cur->lchild ? cur->lchild->size : 0;
long leftLeftSize = cur->lchild && cur->lchild->lchild ? cur->lchild->lchild->size : 0;
long leftRightSize = cur->lchild && cur->lchild->rchild ? cur->lchild->rchild->size : 0;
long rightSize = cur->rchild ? cur->rchild->size : 0;
long rightLeftSize = cur->rchild && cur->rchild->lchild ? cur->rchild->lchild->size : 0;
long rightRightSize = cur->rchild && cur->rchild->rchild ? cur->rchild->rchild->size : 0;
if (leftLeftSize > rightSize) { //LL
cur = rightRotate(cur);
cur->rchild = maintain(cur->rchild);
cur = maintain(cur);
} else if (leftRightSize > rightSize) { //LR
cur->lchild = leftRotate(cur->lchild);
cur = rightRotate(cur);
cur->lchild = maintain(cur->lchild);
cur->rchild = maintain(cur->rchild);
cur = maintain(cur);
} else if (rightRightSize > leftSize) { //RR
cur = leftRotate(cur);
cur->lchild = maintain(cur->lchild);
cur = maintain(cur);
} else if (rightLeftSize > leftSize) { //RL
cur->rchild = rightRotate(cur->rchild);
cur = leftRotate(cur);
cur->lchild = maintain(cur->lchild);
cur->rchild = maintain(cur->rchild);
cur = maintain(cur);
}
return cur;
}
SBTNode *add(SBTNode *cur, long key, bool isExist) {
if (cur == nullptr) {
return new SBTNode(key);
} else {
//无论是否为存在的key,cnt都要更新
cur->cnt++;
if (key == cur->key) { //已经存在的key,则直接返回
return cur;
} else { //不存在的key,则新增节点挂到合适的位置
if (!isExist) {
cur->size++; //不同的key,size才会更新
}
if (key < cur->key) {
cur->lchild = add(cur->lchild, key, isExist);
} else {
cur->rchild = add(cur->rchild, key, isExist);
}
return maintain(cur);
}
}
}
public:
void add(long sum) {
bool isExist = hashSet.count(sum) != 0;
root = add(root, sum, isExist);
hashSet.insert(sum);
}
// <key 的数字个数
long lessKeySize(long key) {
SBTNode *cur = root;
long ans = 0;
while (cur != nullptr) {
if (key == cur->key) {
return ans + (cur->lchild ? cur->lchild->cnt : 0);
} else if (key < cur->key) {
cur = cur->lchild;
} else {
ans += cur->cnt - (cur->rchild ? cur->rchild->cnt : 0);
cur = cur->rchild;
}
}
return ans;
}
// > key 的数字个数
long moreKeySize(long key) {
return root ? (root->cnt - lessKeySize(key + 1)) : 0;
}
};
int countRangeSum(vector<int>& nums, int lower, int upper) {
SizeBalancedTreeSet *treeSet = new SizeBalancedTreeSet();
long sum = 0;
int ans = 0;
treeSet->add(0);
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
long a = treeSet->lessKeySize(sum - lower + 1);
long b = treeSet->lessKeySize(sum - upper);
ans += a - b;
treeSet->add(sum);
}
delete treeSet;
return ans;
}
};
Java版
class Solution {
public static class SBTNode {
public long key; //参与排序的key
public SBTNode l; //左孩子
public SBTNode r; //右孩子
public long size; // 不同key的size,是SBT的平衡因子
public long all; // 总的size,上文提到的附加的数据项,记录经过每个结点的数字个数 (与平衡因子毫无关联)
public SBTNode(long k) {
key = k;
size = 1;
all = 1;
}
}
public static class SizeBalancedTreeSet {
private SBTNode root;
private HashSet<Long> set = new HashSet<>();
private SBTNode rightRotate(SBTNode cur) { //右旋
long same = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0);
SBTNode leftNode = cur.l;
cur.l = leftNode.r;
leftNode.r = cur;
leftNode.size = cur.size;
cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
// all modify
leftNode.all = cur.all; //数据项all也要变更
cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + same;
return leftNode;
}
private SBTNode leftRotate(SBTNode cur) { //左旋
long same = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0);
SBTNode rightNode = cur.r;
cur.r = rightNode.l;
rightNode.l = cur;
rightNode.size = cur.size;
cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
// all modify
rightNode.all = cur.all; //数据项all也要变更
cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + same;
return rightNode;
}
private SBTNode maintain(SBTNode cur) {
if (cur == null) {
return null;
}
long leftSize = cur.l != null ? cur.l.size : 0;
long leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0;
long leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0;
long rightSize = cur.r != null ? cur.r.size : 0;
long rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0;
long rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0;
if (leftLeftSize > rightSize) {
cur = rightRotate(cur);
cur.r = maintain(cur.r);
cur = maintain(cur);
} else if (leftRightSize > rightSize) {
cur.l = leftRotate(cur.l);
cur = rightRotate(cur);
cur.l = maintain(cur.l);
cur.r = maintain(cur.r);
cur = maintain(cur);
} else if (rightRightSize > leftSize) {
cur = leftRotate(cur);
cur.l = maintain(cur.l);
cur = maintain(cur);
} else if (rightLeftSize > leftSize) {
cur.r = rightRotate(cur.r);
cur = leftRotate(cur);
cur.l = maintain(cur.l);
cur.r = maintain(cur.r);
cur = maintain(cur);
}
return cur;
}
private SBTNode add(SBTNode cur, long key, boolean contains) {
if (cur == null) {
return new SBTNode(key);
} else {
cur.all++; //加入新的key的时候,无论该key是否存在,all都要++,
if (key == cur.key) {
return cur;
} else { // 还在左滑或者右滑
if (!contains) {
cur.size++; //只有当之前不存在的key增加时,size才会增加
}
if (key < cur.key) {
cur.l = add(cur.l, key, contains);
} else {
cur.r = add(cur.r, key, contains);
}
return maintain(cur);
}
}
}
public void add(long sum) {
boolean contains = set.contains(sum);
root = add(root, sum, contains);
set.add(sum);
}
//只和二叉搜索树有关,和平衡性无关
public long lessKeySize(long key) { //<key的个数
SBTNode cur = root;
long ans = 0;
while (cur != null) {
if (key == cur.key) { //要的是<key的值,获取左子树上的all
return ans + (cur.l != null ? cur.l.all : 0);
} else if (key < cur.key) { //往左划,不获得
cur = cur.l;
} else { //往右划,则至少该结点及该结点的左子树上的数都是<key的即该结点的all - 右树的all
ans += cur.all - (cur.r != null ? cur.r.all : 0);
cur = cur.r;
}
}
return ans;
}
// > 7 8...
// <8 ...<=7
public long moreKeySize(long key) {
return root != null ? (root.all - lessKeySize(key + 1)) : 0;
}
}
public int countRangeSum(int[] nums, int lower, int upper) {
// 黑盒:加入数字(即前缀和);不去重,可以接受重复数字;可以查询 <num 有几个数
SizeBalancedTreeSet treeSet = new SizeBalancedTreeSet();
long sum = 0;
int ans = 0;
treeSet.add(0);// 一个数都没有的时候,就已经有一个前缀和累加和为0,
for (int i = 0; i < nums.length; i++) { //必须以 i 结尾的情况下子数组达标的数量
sum += nums[i];
// 目标转换为求i之前有多少个前缀和落在[sum - upper, sum - lower]范围上
// 如想查询前缀和在[10, 20]范围的个数
// 本质上是在查询 <10 和 <21 的个数各是多少,然后用<21的数量 - <10的数量,就是前缀和在[10,20]这个范围的个数
long a = treeSet.lessKeySize(sum - lower + 1);
long b = treeSet.lessKeySize(sum - upper);
ans += a - b;
treeSet.add(sum);//当前的前缀和放到结构中
}
return ans;
}
}