【每日一题Day91】LC1825求出 MK 平均值 | 有序集合+模拟

简介: 思路:排序后的数组可以分为三个部分:较小的前k个元素、中间元素、前大的后k个元素,因此使用3个有序集合存储这三个部分。在加入元素时,判断元素的个数以及元素的大小移动元素,时间复杂度为O(logm);在求平均值时,直接求取中间元素的平均值,时间复杂度为O ( 1 )

求出 MK 平均值【LC1825】


给你两个整数 m 和 k ,以及数据流形式的若干整数。你需要实现一个数据结构,计算这个数据流的 MK 平均值


MK 平均值 按照如下步骤计算:


1.如果数据流中的整数少于 m 个,MK 平均值 为 -1 ,否则将数据流中最后 m 个元素拷贝到一个独立的容器中。

2.从这个容器中删除最小的 k 个数和最大的 k 个数。

3.计算剩余元素的平均值,并 向下取整到最近的整数 。


请你实现 MKAverage 类:


  • MKAverage(int m, int k) 用一个空的数据流和两个整数 m 和 k 初始化 MKAverage 对象。
  • void addElement(int num) 往数据流中插入一个新的元素 num 。
  • int calculateMKAverage() 对当前的数据流计算并返回 MK 平均数 ,结果需 向下取整到最近的整数 。


重新投入学习


数组+排序


  • 思路:使用一个长度为m的数组记录数据流的最近m位数据,然后每次计算平均值时,将数据排序,然后计算中间[k+1,m−k−1]个数据之和,然后计算平均值


  • 实现【超时】


i为总共插入的元素下标,通过取余的方式覆盖之前的元素


class MKAverage {
    int[] nums;
    int[] sort;
    int i, m, k;
    public MKAverage(int m, int k) {
        nums = new int[m];
        sort = new int[m];
        i = 0;
        this.m = m;
        this.k = k;
    }
    public void addElement(int num) {
        nums[i % m] = num;
        i++;
    }
    public int calculateMKAverage() {
        if (i < m) return -1;
        for (int i = 0; i < m; i++){
            sort[i] = nums[i];
        }
        Arrays.sort(sort);       
        int sum = 0;
        for (int i = k; i < m - k; i++){
            sum += sort[k];
        }
        return sum / (m - 2 * k);
    }
}
/**
 * Your MKAverage object will be instantiated and called as such:
 * MKAverage obj = new MKAverage(m, k);
 * obj.addElement(num);
 * int param_2 = obj.calculateMKAverage();
 */


。复杂度


  • 时间复杂度:addElement函数的时间复杂度为O(1)。calculateMKAverage函数的时间复杂度为O(mlogm)。


  • 空间复杂度:O ( m )


TreeMap


  • 思路:排序后的数组可以分为三个部分:较小的前k个元素、中间元素、前大的后k个元素,因此使用3个有序集合存储这三个部分。在加入元素时,判断元素的个数以及元素的大小移动元素,时间复杂度为O(logm);在求平均值时,直接求取中间元素的平均值,时间复杂度为O ( 1 ) 。


  • 实现


维护一下数据结构和变量


。使用三个有序集合TreeMapmin,mid,max分别存储最小的k个元素、中间的元素、最大的k个元素,key值为元素大小,value值为元素个数,因此还需要使用额外变量记录集合中元素的个数


。使用一个队列记录当前的m个元素,队列头部为最早加入的元素,队尾元素为最近加入的元素


。使用一个int类型变量记录中间元素之和


函数实现


。addElement


1.加入元素num值对应位置,并放入队列中:

  • 如果min为空,或者num≤max(min),则将num加入min中
  • 否则,如果max为空,或者num≥min(max),则将num加入max中
  • 否则,则将num加入mid中,sum增加num


2.移除元素:

  • 如果队列的长度大于m,那么移除队首元素,查找队首元素位于哪个集合中,并从该集合中删除该元素,如果该集合为mid,则sum减去队首元素


  • 并判断三个集合中的元素个数是否大于或者小于k,若大于k,那么先移动元素至mid,再将元素移动至min或者max


  • 如果min的长度大于k,则将最大值放入mid中
  • 如果max的长度大于k,则将最小值放入mid中
  • 如果min的长度小于k,则将mid的最小值放入min中
  • 如果max的长度小于k,则将mid的最大值放入max中


。calculateMKAverage


如果队列长度小于m,那么返回-1,否则返回sum/(m−2∗k)


  • 实现


class MKAverage {
    TreeMap<Integer,Integer> min = new TreeMap<>();
    TreeMap<Integer,Integer> mid = new TreeMap<>();
    TreeMap<Integer,Integer> max = new TreeMap<>();
    Deque<Integer> dq = new LinkedList<>();
    int sum = 0, s1 = 0, s2 = 0, s3 = 0;
    int m, k;
    public MKAverage(int m, int k) {
        this.m = m;
        this.k = k;
    }
    public void addElement(int num) {
        // 放入有序集合中
        if (s1 == 0 || num <= min.lastKey()){
            min.merge(num, 1, Integer::sum);
            s1++;
        }else if (s3 == 0 || num >= max.firstKey()){
            max.merge(num, 1, Integer::sum);
            s3++;
        }else{
            mid.merge(num, 1, Integer::sum);
            sum += num;
            s2++;
        }
        // 放入队列中
        dq.addLast(num);
        // 删除队首元素
        if (dq.size() > m){
            int poll = dq.pollFirst();
            if (min.containsKey(poll)){
                if (min.merge(poll, -1, Integer::sum) == 0){
                    min.remove(poll);
                }
                s1--;
            }else if (max.containsKey(poll)){
                if (max.merge(poll, -1, Integer::sum) == 0){
                    max.remove(poll);
                }
                s3--;
            }else{
                if (mid.merge(poll, -1, Integer::sum) == 0){
                    mid.remove(poll);
                }
                s2--;
                sum -= poll;
            }
        }
        // 调整有序集合中的元素
        while (s1 > k){
            int x = min.lastKey();
            if (min.merge(x, -1 ,Integer::sum) == 0){
                min.remove(x);
            }
            mid.merge(x, 1, Integer::sum);
            sum += x;
            s2++;
            s1--;
        }
        while (s3 > k){
            int x = max.firstKey();
            if (max.merge(x, -1 ,Integer::sum) == 0){
                max.remove(x);
            }
            mid.merge(x, 1, Integer::sum);
            sum += x;
            s2++;
            s3--;
        }
        while (s1 < k && !mid.isEmpty()){
            int x = mid.firstKey();
            if (mid.merge(x, -1 ,Integer::sum) == 0){
                mid.remove(x);
            }
            sum -= x;
            min.merge(x, 1, Integer::sum);
            s2--;           
            s1++;
        }
        while (s3 < k && !mid.isEmpty()){
            int x = mid.lastKey();
            if (mid.merge(x, -1 ,Integer::sum) == 0){
                mid.remove(x);
            }
            sum -= x;
            max.merge(x, 1, Integer::sum);
            s2--;           
            s3++;
        }
    }
    public int calculateMKAverage() {
        if (dq.size() < m){
            return -1;
        }
        return sum / (m - 2 * k);
    }
}
/**
 * Your MKAverage object will be instantiated and called as such:
 * MKAverage obj = new MKAverage(m, k);
 * obj.addElement(num);
 * int param_2 = obj.calculateMKAverage();
 */
目录
相关文章
|
5月前
【每日一题Day221】LC2455可被三整除的偶数的平均值 | 模拟
【每日一题Day221】LC2455可被三整除的偶数的平均值 | 模拟
47 0
|
5月前
【每日一题Day345】LC2562找出数组的串联值 | 模拟
【每日一题Day345】LC2562找出数组的串联值 | 模拟
38 0
|
5月前
【每日一题Day155】LC1630等差子数组 | 枚举+排序
【每日一题Day155】LC1630等差子数组 | 枚举+排序
39 0
|
5月前
【每日一题Day292】LC1572矩阵对角线元素的和 模拟
【每日一题Day292】LC1572矩阵对角线元素的和 模拟
26 0
|
5月前
【每日一题Day133】LC2373矩阵中的局部最大值 | 模拟
【每日一题Day133】LC2373矩阵中的局部最大值 | 模拟
47 0
|
5月前
【每日一题Day278】LC2500删除每行中的最大值 | 排序+模拟
【每日一题Day278】LC2500删除每行中的最大值 | 排序+模拟
45 0
|
5月前
【每日一题Day255】LC2679矩阵中的和 | 排序
【每日一题Day255】LC2679矩阵中的和 | 排序
26 0
|
5月前
【每日一题Day358】LC2698求一个整数的惩罚数 | 递归
【每日一题Day358】LC2698求一个整数的惩罚数 | 递归
50 0
|
5月前
【每日一题Day277】LC2569更新数组后处理求和查询 | 线段树
【每日一题Day277】LC2569更新数组后处理求和查询 | 线段树
28 0
|
5月前
【每日一题Day308】LC57插入区间 | 模拟
【每日一题Day308】LC57插入区间 | 模拟
45 0