{x}
blog image

Finding MK Average

You are given two integers, m and k, and a stream of integers. You are tasked to implement a data structure that calculates the MKAverage for the stream.

The MKAverage can be calculated using these steps:

  1. If the number of the elements in the stream is less than m you should consider the MKAverage to be -1. Otherwise, copy the last m elements of the stream to a separate container.
  2. Remove the smallest k elements and the largest k elements from the container.
  3. Calculate the average value for the rest of the elements rounded down to the nearest integer.

Implement the MKAverage class:

  • MKAverage(int m, int k) Initializes the MKAverage object with an empty stream and the two integers m and k.
  • void addElement(int num) Inserts a new element num into the stream.
  • int calculateMKAverage() Calculates and returns the MKAverage for the current stream rounded down to the nearest integer.

 

Example 1:

Input
["MKAverage", "addElement", "addElement", "calculateMKAverage", "addElement", "calculateMKAverage", "addElement", "addElement", "addElement", "calculateMKAverage"]
[[3, 1], [3], [1], [], [10], [], [5], [5], [5], []]
Output
[null, null, null, -1, null, 3, null, null, null, 5]

Explanation
MKAverage obj = new MKAverage(3, 1); 
obj.addElement(3);        // current elements are [3]
obj.addElement(1);        // current elements are [3,1]
obj.calculateMKAverage(); // return -1, because m = 3 and only 2 elements exist.
obj.addElement(10);       // current elements are [3,1,10]
obj.calculateMKAverage(); // The last 3 elements are [3,1,10].
                          // After removing smallest and largest 1 element the container will be [3].
                          // The average of [3] equals 3/1 = 3, return 3
obj.addElement(5);        // current elements are [3,1,10,5]
obj.addElement(5);        // current elements are [3,1,10,5,5]
obj.addElement(5);        // current elements are [3,1,10,5,5,5]
obj.calculateMKAverage(); // The last 3 elements are [5,5,5].
                          // After removing smallest and largest 1 element the container will be [5].
                          // The average of [5] equals 5/1 = 5, return 5

 

Constraints:

  • 3 <= m <= 105
  • 1 <= k*2 < m
  • 1 <= num <= 105
  • At most 105 calls will be made to addElement and calculateMKAverage.

Solution Explanation: Finding MK Average

This problem requires designing a data structure that efficiently calculates the MKAverage of a stream of integers. The MKAverage is calculated by taking the last m elements, removing the smallest and largest k elements, and then averaging the remaining elements.

The optimal solution leverages the properties of ordered sets (or sorted containers) and queues to achieve efficient addition and calculation of the average. Let's break down the approach:

Data Structures:

  • Queue (q): A deque (double-ended queue) is used to store the stream of integers. This allows for O(1) time complexity for adding elements at the end and removing elements from the beginning. The queue maintains the last m elements.

  • Ordered Sets (lo, mid, hi): Three ordered sets (e.g., SortedList in Python, TreeMap in Java, multiset in C++, or a self-balancing binary search tree in Go) are used to efficiently manage the smallest k elements (lo), the middle elements (mid), and the largest k elements (hi). These sets provide O(log m) time complexity for insertion, deletion, and finding min/max elements.

  • Sum (s): A variable s keeps track of the sum of elements in the mid set. This is crucial for efficiently calculating the average.

Algorithm:

  1. __init__(m, k): Initializes the data structures with m and k.

  2. addElement(num):

    • Add the new element num to the queue q.
    • If the queue's size exceeds m, remove the oldest element from the queue and update the ordered sets and s accordingly. This is done by checking which set the removed element belongs to and adjusting the sets and sum.
    • Insert the new element num into one of the three ordered sets (lo, mid, or hi) based on its value relative to the existing elements.
    • Maintain the sizes of lo and hi to be less than or equal to k. If they exceed k, transfer elements from lo or hi to mid, updating s.
    • Similarly, if lo or hi have fewer than k elements, transfer elements from mid to lo or hi, updating s.
  3. calculateMKAverage():

    • If the queue size is less than m, return -1 (not enough elements).
    • Otherwise, return the integer floor of s / (m - 2 * k), which is the average of the remaining elements after removing the smallest and largest k elements.

Time Complexity Analysis:

  • addElement(num): O(log m) due to the operations on the ordered sets.
  • calculateMKAverage(): O(1) as it directly uses the pre-calculated sum s.

Space Complexity Analysis: O(m) to store the queue and the elements in the ordered sets.

Code Examples (Python): (Other languages are similar in structure)

from sortedcontainers import SortedList
from collections import deque
 
class MKAverage:
    def __init__(self, m: int, k: int):
        self.m = m
        self.k = k
        self.q = deque()
        self.lo = SortedList()
        self.mid = SortedList()
        self.hi = SortedList()
        self.s = 0
 
    def addElement(self, num: int) -> None:
        self.q.append(num)
        if len(self.q) > self.m:
            removed = self.q.popleft()
            if removed in self.lo:
                self.lo.remove(removed)
            elif removed in self.mid:
                self.mid.remove(removed)
                self.s -= removed
            else:
                self.hi.remove(removed)
        # ... (rest of addElement logic as described above) ...
 
    def calculateMKAverage(self) -> int:
        if len(self.q) < self.m:
            return -1
        # ... (calculation logic as described above) ...
 

This detailed explanation provides a comprehensive understanding of the algorithm, data structures, and complexity analysis for solving the "Finding MK Average" problem efficiently. Remember to adapt the code to your preferred programming language, using appropriate ordered set implementations.