Leetcode 1825 对数据流求解MK值

来源:1-5 大厂面试为什么总考算法?

甲骨文_0001

2023-01-18

/**
 * @param {number} m
 * @param {number} k
 */
var MKAverage = function(m, k) {
    this.data = [];
    this.m = m;
    this.k = k;
};
/** 
 * @param {number} num
 * @return {void}
 */
MKAverage.prototype.addElement = function(num) {
    // 先添加
    this.data.push(num);
};
/**
 * @return {number}
 */
MKAverage.prototype.calculateMKAverage = function() {
    // < m, =>-1
    if( this.data.length < this.m ){
        return -1;
    }

    // 数据流this.data元素大于m个
    let mArr = [];
    for( let i = this.data.length - this.m; i < this.data.length; i ++ ){
        mArr.push(this.data[i]);
    } // for i

    // 对mArr进行从小到大排序
    mArr.sort((a, b)=>a - b);

    // mArr是有序数组, 从小到大
    // 先计算总和
    let totalSum = mArr.reduce((tmp, cur)=>tmp + cur);
    // 最小的k个数
    let minKArr = [];
    for( let i = 0; i < this.k; i ++ ){
        minKArr.push(mArr[i]);
    } // for i
    let minSum = minKArr.reduce((tmp, cur)=>tmp + cur); // 最小k个数之和

    // 最大的k个数
    let maxKArr = [];
    for( let i = mArr.length - this.k; i < mArr.length; i ++ ){
        maxKArr.push( mArr[i] );
    } // for i
    let maxSum = maxKArr.reduce((tmp, cur)=>tmp+cur); // 最大k个数之和

    let leftSum = totalSum - minSum - maxSum;
    let leftNum = mArr.length - this.k - this.k;

    return Math.floor( leftSum / leftNum );

};

/**
 * Your MKAverage object will be instantiated and called as such:
 * var obj = new MKAverage(m, k)
 * obj.addElement(num)
 * var param_2 = obj.calculateMKAverage()
 */

图片描述
老师,我想我的方法应该是正确的,只是超时了,我采取的比较直接,对数据流 this.data,每进来一个数据, this.data.push(element), 之后,如果要求解calculateMKAverage, 那么我就取出this.data的最后 m 个元素拷贝到一个独立的容器(mArr)中,然后对mArr进行从小到大排序,然后减掉最大与最小的k个元素,再取平均值。因为数据规模是 10^5,级别,脑海中也想到了超时,但不知道更好的方案,哈哈哈。老师,指导下哈。另外祝老师还有您的家人新年快乐: )

写回答

1回答

liuyubobobo

2023-01-19

其实你问的很多问题都有类似之处,简而言之就是,对于每一个 query,去暴力做肯定不行(要是这就可以的话,这个问题的意义就不大了。)

优化的思路基本上都是:解决当前的 query,能否利用之前 query 的信息,而不是重新完整计算一次。


==========


根据题意:


1)你需要能够快速获得每个元素进来的时间,以快速的在有 m + 1 个元素的时候,删除掉最早进来的一个元素,保证当前的集合里只有 m 个元素;

双端队列可以满足这个需求。


2)你需要能够快速获得当前的 m 个元素的最小的 k 个值,最大的 k 个值,和中间的 m - 2 * k 个值;并且根据 1),你需要在新来一个元素,或者扔掉一个元素的时候,能够维护这三个“集合”。

叙述方便,我叫最小 k 个值的集合是 A;中间 m - 2 * k 个值的集合是 B;最大 k 个值的集合是 C。

这里的重点是要“维护”,你可以想象,为了维护这三个集合,这三个集合中的元素需要互相“腾挪”。比如 A 集合里多一个元素的话(有 k + 1 个元素),就需要把 A 集合的最大值扔到 B 集合,然后再维护 B 集合,以此类推。

所以,存储这些集合的数据结构,需要能快速获得当前集合的最大值或者最小值,包括能快速做增删查。

使用红黑树最合适。


3)你需要能快速计算集合 B 中的元素和。这很简单,在维护集合 B 的时候,同时维护集合 B 的和就好了。


==========


数据结构定下来了,下面的实现在我看来其实挺机械的。我不排除我的代码写复杂了,但整体思路应该很清晰,可以参考:https://github.com/liuyubobobo/Play-Leetcode/blob/master/1501-2000/1825-Finding-MK-Average/cpp-1825/main.cpp


其中“看似”最复杂的两个函数 remove 和 add,就是在新来一个元素的时候,维护 A, B, C(在我的代码中叫 minTree, middleTree 和 maxTree)。

在删除的时候,如果是从 C 中删除,直接删;(42-46)

如果从 B 中删除,把 C 中的最小元素扔到 B;(48-62)

如果从 A 中删除,把 B 中的最小元素扔到 A,再把 C 中的最小元素扔到 B (64-81)


在添加的时候,直接添加到 A (86)

如果 A 的元素多了,把 A 中最大的元素扔到 B;(88-94)

如果 B 中的元素多了,把 B 中的元素扔到 C (96-102)


在这个基础上,题目要求的那两个函数应该是非常简单的:

addElement (27-38)

calculateMK(105-108)


==========


继续加油!:)

1
2
liuyubobobo
回复
甲骨文_0001
新年快乐!:)
2023-01-19
共2条回复

玩转算法面试-- Leetcode真题分门别类讲解

课程配套大量BAT面试真题,高频算法题解析,强化训练

7459 学习 · 1159 问题

查看课程