力扣 1825 hard题:求出MK平均值

力扣 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个)会出现超时,超时截图如下图所示:
method1超时
因此,可以得出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:
  1. 元素大于setMiddle的最后一个元素,则插入setLastK中,并将setLastK的第一个元素放入setMiddle中保证middle中元素个数为m - 2k + 1
  2. 元素小于setMiddle的第一个元素,则插入setFrontK中,并将setFrontK的最后一个元素放入setMiddle中
  3. 若上两者均不是,则直接插入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)的时间复杂度,成功通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值