力扣 1825 hard题:求出MK平均值
题目
给你两个整数 m 和 k ,以及数据流形式的若干整数。你需要实现一个数据结构,计算这个数据流的 MK 平均值 。
MK 平均值 按照如下步骤计算:
如果数据流中的整数少于 m 个,MK 平均值 为 -1 ,否则将数据流中最后 m 个元素拷贝到一个独立的容器中。
从这个容器中删除最小的 k 个数和最大的 k 个数。
计算剩余元素的平均值,并 向下取整到最近的整数 。
请你实现 MKAverage 类:
MKAverage(int m, int k) 用一个空的数据流和两个整数 m 和 k 初始化 MKAverage 对象。
void addElement(int num) 往数据流中插入一个新的元素 num 。
int calculateMKAverage() 对当前的数据流计算并返回 MK 平均数 ,结果需 向下取整到最近的整数 。
一、题目分析
本题比较抽象,题目描述的也比较长,不太容易理解讲到数据流和容器等概念
,但实际上这些概念并不影响解题。本题可以简单的理解为实现一个类,能够完成实例所实现的功能即可,示例如下所示:
输入:
[“MKAverage”, “addElement”, “addElement”, “calculateMKAverage”, “addElement”, “calculateMKAverage”, “addElement”, “addElement”, “addElement”, “calculateMKAverage”]
[[3, 1], [3], [1], [], [10], [], [5], [5], [5], []]
输出:
[null, null, null, -1, null, 3, null, null, null, 5]
解释:
MKAverage obj = new MKAverage(3, 1);
obj.addElement(3); // 当前元素为 [3]
obj.addElement(1); // 当前元素为 [3,1]
obj.calculateMKAverage(); // 返回 -1 ,因为 m = 3 ,但数据流中只有 2 个元素
obj.addElement(10); // 当前元素为 [3,1,10]
obj.calculateMKAverage(); // 最后 3 个元素为 [3,1,10] , 删除最小以及最大的 1 个元素后,容器为 [3],[3] 的平均值等于 3/1 = 3 ,故返回 3
obj.addElement(5); // 当前元素为 [3,1,10,5]
obj.addElement(5); // 当前元素为 [3,1,10,5,5]
obj.addElement(5); // 当前元素为 [3,1,10,5,5,5]
obj.calculateMKAverage(); // 最后 3 个元素为 [5,5,5];删除最小以及最大的 1 个元素后,容器为 [5]; [5] 的平均值等于 5/1 = 5 ,故返回 5
二、解题思路
method1.数组+排序实现
方法一采用数组+排序的方法来实现,即数据流和容器都用数组实现
每次addElement操作时,向数组插入数据,时间复杂度0(1)
每次calculateMKAverage时,提取数组的后m个数据,并排序,随后计算除这m个数据中除前k个数据和后k个数据的平均值,即是该问题需要求解的答案,本操作的时间复杂度达到O(mlogm)
c++代码如下:
class MKAverage {
private:
int globalK = 0;
int globalM = 0;
vector<int> array;
vector<int> capacity;
public:
MKAverage(int m, int k) : globalM(m),globalK(k){
capacity.resize(m);
}
void addElement(int num) {
array.push_back(num);
}
int calculateMKAverage() {
if(array.size() < globalM){
return -1;
}
for(int i = 0;i < globalM;i++){
capacity[i] = array[array.size() - i - 1];
}
sort(capacity.begin(),capacity.end());
int sum = 0;
// for(int i = 0;i < globalM;i++){
// cout << capacity[i] << endl;;
// }
for(int i = 0;i < capacity.size();i++){
if(i < globalK || i > capacity.size() - globalK - 1){
continue;
}
sum += capacity[i];
}
// cout << "sum为" << sum << endl;
return sum / (capacity.size() - 2 * globalK);
}
};
method1过测试用例没有问题,但在提交时第13个用例(共17个)会出现超时,超时截图如下图所示:
因此,可以得出mlogm的时间复杂度不足以过全部测试用例,所以要用新的方法,降低解题的时间复杂度。
method2队列+set(排序)
因为本题其实不需要保存从开始到结束的addelement,仅需要保存后插入的m个element即可,因此用队列queue保存即可。
将队列中m个元素中前k小、中间m-2k以及前k大的元素分别存入三个set中,分别命名为setFrontK、setMiddle、setLastK。
在插入新的元素后,进行判断:
每一步在setMiddle中插入元素或者删除元素时,调整该set的和
- 若queue.size <= m,则元素数不够,addelement时,则直接插入setMiddle即可。如若queue.size() == m则代表元素数已经>=m,则将setMiddle中的前k个和后k个元素分别放入setFrontK和setLastK中。
- 若queue.size > m,则元素数大于m,此时根据元素的大小来插入set:
- 元素大于setMiddle的最后一个元素,则插入setLastK中,并将setLastK的第一个元素放入setMiddle中
保证middle中元素个数为m - 2k + 1
- 元素小于setMiddle的第一个元素,则插入setFrontK中,并将setFrontK的最后一个元素放入setMiddle中
- 若上两者均不是,则直接插入setMiddle中
此时,还需要删除倒数第m+1个元素(在队列中pop)并判断该元素在哪一个set中:
- 在setFrontK中,删除该元素,并将setMiddle中的第一个元素放入setFrontK中,
- 在setLastK中,删除该元素,并将setMiddle中的最后一个元素放入setLastK中。
- 否则,直接在setMiddle中删除该元素。
代码如下:
class MKAverage {
private:
int globalK = 0;
int globalM = 0;
long sum = 0;
queue<int> queueM;
multiset<int> setFrontK,setMiddle,setLastK;
public:
MKAverage(int m, int k) : globalM(m),globalK(k){}
void addElement(int num) {
queueM.push(num);
if(queueM.size() <= globalM){
setMiddle.insert(num);
sum += num;
if(queueM.size() == globalM){
while(setFrontK.size() < globalK){
setFrontK.insert(*setMiddle.begin());
sum -= *setMiddle.begin();
setMiddle.erase(setMiddle.begin());
}
while(setLastK.size() < globalK){
setLastK.insert(*setMiddle.rbegin());
sum -= *setMiddle.rbegin();
setMiddle.erase(--setMiddle.end());
// cout << sum << endl;
}
}
return;
}
if(num < *setMiddle.begin()){
setFrontK.insert(num);
setMiddle.insert(*setFrontK.rbegin());
sum += *setFrontK.rbegin();
setFrontK.erase(--setFrontK.end());
}
else if(num > *setMiddle.rbegin()){
setLastK.insert(num);
setMiddle.insert(*setLastK.begin());
sum += *setLastK.begin();
setLastK.erase(setLastK.begin());
}
else{
setMiddle.insert(num);
sum += num;
}
//每次插入一个数,就要删除一个数(因为有一个数已不在后m个数之中
num = queueM.front();
queueM.pop();
if(setFrontK.count(num)){
setFrontK.erase(setFrontK.find(num));
sum -= *setMiddle.begin();
setFrontK.insert(*setMiddle.begin());
}
else if(setLastK.count(num)){
setLastK.erase(setLastK.find(num));
sum -= *setMiddle.rbegin();
setLastK.insert(*setMiddle.rbegin());
}
else{
sum -= num;
setMiddle.erase(setMiddle.find(num));
}
}
int calculateMKAverage() {
if(queueM.size() < globalM){
return -1;
}
return sum /(globalM - 2 * globalK);
}
};
在该代码中,addElement的时间复杂度为O(m),calculateMKAverage时间复杂度为O(1)。注意本题中使用multiSet而非set,multiset允许重复的值,set不允许
总结
本题主要是完成一个类,实现题目规定的功能即可。主要还是时间复杂度能不能通过,刚开始O(mlogm)的时间复杂度无法通过测试用例,后来改进成使用multiSet和queue配合降为O(m)的时间复杂度,成功通过。