Talentd
FleetCode
Make and follow sheets.Sign in

Finding MK Average solution and explanation

HardData StreamDesignHeap (Priority Queue)Ordered Set10 min read38.7% acceptanceUpdated 9 Apr 2026
LeetCode 1825. Finding MK AverageHappy Coding · 24:44 · 4K viewsWatch on YouTube
8 more videos on this problem
  1. LeetCode 1825. Finding MK AverageHappy Coding · 24:44 · 4K views
  2. 1825. Finding MK Average (Leetcode Hard)Programming Live with Larry · 35:25 · 1K views
  3. 【小小福讲Leetcode】LeeCode 1825. Finding MK Average | Weekly Contest 263小小福LeetCode · 26:15 · 1K views
  4. 【每日一题】1825. Finding MK Average, 4/14/2021Huifeng Guan · 32:15 · 980 views
  5. Leetcode 1825. Finding MK AverageFearless Learner · 17:54 · 867 views
  6. LeetCode 1825. Finding MK Average | Weekly Contest 236宰相小甘罗 · 8:11 · 829 views
  7. LeetCode 1825. Finding MK Average 中文 | Weekly Contest 236宰相小甘罗 · 9:06 · 370 views
  8. Google Interview Problem | Finding MK Average | Leetcode HardNikhil Gupta · 13:50 · 306 views
  9. Finding Mk average: Best Interview Thought processC-mmon · 25:41 · 105 views

The problem

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.

Approach overview

Problem Overview: Design a data structure that processes a stream of integers and returns the MKAverage of the last m elements. The MKAverage removes the k smallest and k largest values from the window and averages the remaining numbers. The structure must support continuous insertions and efficient queries.

Approach 1: Sorting the Window (O(m log m) time, O(m) space)

Maintain a queue containing the last m elements from the stream. Each time you need to compute the MKAverage, copy the elements into an array and sort it. After sorting, ignore the first k and last k elements, then compute the sum of the remaining range and divide by m - 2k. The key idea is brute-force ordering: sorting reveals which values should be removed from both ends. This approach is straightforward and useful for validating logic, but each query costs O(m log m), which becomes expensive when the stream grows. Space complexity stays O(m) because only the sliding window is stored.

Approach 2: Balanced Tree Partitioning (O(log m) time per update, O(m) space)

Use three balanced ordered sets (or multisets) to maintain the smallest k, the largest k, and the middle m - 2k elements. The middle group also tracks its running sum so the MKAverage can be computed in constant time. When a new element arrives, insert it into the appropriate set and rebalance so the sizes remain exactly k, m - 2k, and k. When the window exceeds m, remove the oldest element from whichever set it belongs to and rebalance again. Balanced tree operations like insertion, deletion, and boundary lookup take O(log m). This keeps updates efficient while preserving sorted order. The technique relies heavily on ordered sets and works well for streaming problems in data stream systems.

Recommended for interviews: The balanced tree approach is what interviewers expect for a hard design problem. The sorting solution demonstrates understanding of the MKAverage definition, but it does not scale well. Implementing three ordered partitions with a running sum shows strong control of design patterns and efficient updates in a sliding window.

Approach 1: Approach Using Sorting

This approach uses a sliding window to keep track of the last m elements inserted. When calculateMKAverage is called, we sort the window and remove the smallest k and largest k elements to compute the average. Although sorting is an O(m log m) operation, this implementation is straightforward and intuitive.

import collections

class MKAverage:
    def __init__(self, m: int, k: int):
        self.m = m
        self.k = k
        self.stream = collections.deque()

    def addElement(self, num: int) -> None:
        self.stream.append(num)
        if len(self.stream) > self.m:
            self.stream.popleft()

    def calculateMKAverage(self) -> int:
        if len(self.stream) < self.m:
            return -1
        sorted_stream = sorted(self.stream)
        trimmed_stream = sorted_stream[self.k:self.m - self.k]
        return sum(trimmed_stream) // len(trimmed_stream)
#include <deque>
#include <vector>
#include <algorithm>

class MKAverage {
private:
    int m, k;
    std::deque<int> stream;

public:
    MKAverage(int m, int k) : m(m), k(k) {}

    void addElement(int num) {
        stream.push_back(num);
        if (stream.size() > m) {
            stream.pop_front();
        }
    }

    int calculateMKAverage() {
        if (stream.size() < m) {
            return -1;
        }
        std::vector<int> sorted_stream(stream.begin(), stream.end());
        std::sort(sorted_stream.begin(), sorted_stream.end());
        int sum = 0;
        for (int i = k; i < m - k; ++i) {
            sum += sorted_stream[i];
        }
        return sum / (m - 2 * k);
    }
};

The Python solution utilizes a deque to efficiently manage the sliding window. On adding a new element, we append it to the deque and pop elements from the left if the size exceeds m. For calculating the MKAverage, once we confirm the number of elements is sufficient, we sort the window, remove the smallest and largest k elements, and return the integer division of the sum of remaining elements by their count.

Complexity

The time complexity for adding an element is O(1). The time complexity for calculating the MKAverage is O(m log m) due to sorting. The space complexity is O(m) as we store at most m elements.

Try this approach in the editor

Approach 2: Approach Using Balanced Tree

This approach uses balanced binary search trees or any self-balancing data structure to efficiently manage the m elements along with retrieving/removing the smallest/largest k elements. While this complexity is higher for each insert, it provides a more efficient way to handle element removals and retrieval.

import java.util.TreeMap;

class MKAverage {
    private int m, k, elementCount;
    private TreeMap<Integer, Integer> window;
    private TreeMap<Integer, Integer> small;
    private TreeMap<Integer, Integer> large;
    private long smallSum, largeSum, totalSum;

    public MKAverage(int m, int k) {
        this.m = m;
        this.k = k;
        this.window = new TreeMap<>();
        this.small = new TreeMap<>();
        this.large = new TreeMap<>();
        this.elementCount = 0;
    }

    public void addElement(int num) {
        // Implement logic to maintain the elements with TreeMaps
        totalSum += num;
        window.put(num, window.getOrDefault(num, 0) + 1);

        if (elementCount >= m) { // remove oldest element logic
            int oldestElement = window.firstKey(); // assume logic to get old element correctly
            totalSum -= oldestElement;
            // Implement logic to remove oldestElement from the data structures
        }

        // Implement logic to balance small, large, and window elements

        elementCount++;
    }

    public int calculateMKAverage() {
        if (elementCount < m) {
            return -1;
        }
        return (int) ((totalSum - smallSum - largeSum) / (m - 2 * k));
    }

    // Helper functions to maintain balance
}
class MKAverage {
    constructor(m, k) {
        this.m = m;
        this.k = k;
        this.stream = [];
    }

    addElement(num) {
        this.stream.push(num);
        if (this.stream.length > this.m) {
            this.stream.shift();
        }
    }

    calculateMKAverage() {
        if (this.stream.length < this.m) {
            return -1;
        }
        const sortedStream = [...this.stream].sort((a, b) => a - b);
        const trimmedStream = sortedStream.slice(this.k, this.m - this.k);
        const sum = trimmedStream.reduce((acc, v) => acc + v, 0);
        return Math.floor(sum / trimmedStream.length);
    }
}

The Java solution uses a TreeMap to simulate a balanced binary search tree, allowing efficient insertion, deletion, and access to elements. While TreeMap in Java maintains a sorted order, additional logic is needed to ensure we handle groups of smallest and largest k elements. As elements are added beyond size m, the oldest elements are removed, and rebalance operations are performed.

Complexity

The time complexity for each operation (add or calculate) is O(log m) due to balanced tree operations. The space complexity is O(m) for storing the elements within the m-sized window.

Try this approach in the editor

Approach 3: Ordered Set + Queue

We can maintain the following data structures or variables:

  • A queue q of length m, where the head of the queue is the earliest added element, and the tail of the queue is the most recently added element;
  • Three ordered sets, namely lo, mid, hi, where lo and hi store the smallest k elements and the largest k elements respectively, and mid stores the remaining elements;
  • A variable s, maintaining the sum of all elements in mid;
  • Some programming languages (such as Java, Go) additionally maintain two variables size1 and size3, representing the number of elements in lo and hi respectively.

When calling the addElement(num) function, perform the following operations in order:

  1. If lo is empty, or num leq max(lo), then add num to lo; otherwise if hi is empty, or num geq min(hi), then add num to hi; otherwise add num to mid, and add the value of num to s.
  2. Next, add num to the queue q. If the length of the queue q is greater than m at this time, remove the head element x from the queue q, then choose one of lo, mid or hi that contains x, and remove x from this set. If the set is mid, subtract the value of x from s.
  3. If the length of lo is greater than k, then repeatedly remove the maximum value max(lo) from lo, add max(lo) to mid, and add the value of max(lo) to s.
  4. If the length of hi is greater than k, then repeatedly remove the minimum value min(hi) from hi, add min(hi) to mid, and add the value of min(hi) to s.
  5. If the length of lo is less than k and mid is not empty, then repeatedly remove the minimum value min(mid) from mid, add min(mid) to lo, and subtract the value of min(mid) from s.
  6. If the length of hi is less than k and mid is not empty, then repeatedly remove the maximum value max(mid) from mid, add max(mid) to hi, and subtract the value of max(mid) from s.

When calling the calculateMKAverage() function, if the length of q is less than m, return -1, otherwise return \frac{s}{m - 2k}.

In terms of time complexity, each call to the addElement(num) function has a time complexity of O(log m), and each call to the calculateMKAverage() function has a time complexity of O(1). The space complexity is O(m).

type MKAverage struct {
	lo, mid, hi  *redblacktree.Tree
	q            []int
	m, k, s      int
	size1, size3 int
}

func Constructor(m int, k int) MKAverage {
	lo := redblacktree.NewWithIntComparator()
	mid := redblacktree.NewWithIntComparator()
	hi := redblacktree.NewWithIntComparator()
	return MKAverage{lo, mid, hi, []int{}, m, k, 0, 0, 0}
}

func (this *MKAverage) AddElement(num int) {
	merge := func(rbt *redblacktree.Tree, key, value int) {
		if v, ok := rbt.Get(key); ok {
			nxt := v.(int) + value
			if nxt == 0 {
				rbt.Remove(key)
			} else {
				rbt.Put(key, nxt)
			}
		} else {
			rbt.Put(key, value)
		}
	}

	if this.lo.Empty() || num <= this.lo.Right().Key.(int) {
		merge(this.lo, num, 1)
		this.size1++
	} else if this.hi.Empty() || num >= this.hi.Left().Key.(int) {
		merge(this.hi, num, 1)
		this.size3++
	} else {
		merge(this.mid, num, 1)
		this.s += num
	}
	this.q = append(this.q, num)
	if len(this.q) > this.m {
		x := this.q[0]
		this.q = this.q[1:]
		if _, ok := this.lo.Get(x); ok {
			merge(this.lo, x, -1)
			this.size1--
		} else if _, ok := this.hi.Get(x); ok {
			merge(this.hi, x, -1)
			this.size3--
		} else {
			merge(this.mid, x, -1)
			this.s -= x
		}
	}
	for ; this.size1 > this.k; this.size1-- {
		x := this.lo.Right().Key.(int)
		merge(this.lo, x, -1)
		merge(this.mid, x, 1)
		this.s += x
	}
	for ; this.size3 > this.k; this.size3-- {
		x := this.hi.Left().Key.(int)
		merge(this.hi, x, -1)
		merge(this.mid, x, 1)
		this.s += x
	}
	for ; this.size1 < this.k && !this.mid.Empty(); this.size1++ {
		x := this.mid.Left().Key.(int)
		merge(this.mid, x, -1)
		this.s -= x
		merge(this.lo, x, 1)
	}
	for ; this.size3 < this.k && !this.mid.Empty(); this.size3++ {
		x := this.mid.Right().Key.(int)
		merge(this.mid, x, -1)
		this.s -= x
		merge(this.hi, x, 1)
	}
}

func (this *MKAverage) CalculateMKAverage() int {
	if len(this.q) < this.m {
		return -1
	}
	return this.s / (this.m - 2*this.k)
}

/**
 * Your MKAverage object will be instantiated and called as such:
 * obj := Constructor(m, k);
 * obj.AddElement(num);
 * param_2 := obj.CalculateMKAverage();
 */
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach Using Sorting

The time complexity for adding an element is O(1). The time complexity for calculating the MKAverage is O(m log m) due to sorting. The space complexity is O(m) as we store at most m elements.

Approach Using Balanced Tree

The time complexity for each operation (add or calculate) is O(log m) due to balanced tree operations. The space complexity is O(m) for storing the elements within the m-sized window.

Ordered Set + Queue–

Complexity analysis

ApproachTimeSpaceWhen to Use
Sorting the WindowO(m log m) per calculationO(m)Simple implementation or small window sizes where performance is not critical
Balanced Tree PartitioningO(log m) per add, O(1) queryO(m)Optimal for large data streams requiring frequent updates and fast MKAverage queries

Frequently asked questions

Is Finding MK Average easy or hard?

Finding MK Average is classified as a hard problem because it combines sliding window logic, ordered data structures, and dynamic rebalancing. Efficient solutions require careful handling of insertions, deletions, and maintaining the middle segment sum.

Finding MK Average Python/Java solution

Python implementations often simulate ordered sets using SortedList from the sortedcontainers library or multiple heaps with careful balancing. Java solutions commonly use TreeMap or TreeSet structures to maintain three partitions and track the running sum of the middle elements.

How to solve Finding MK Average in O(n)?

The problem is typically solved with O(log m) updates rather than strict O(n) overall complexity because each stream insertion requires rebalancing ordered sets. Maintaining three partitions and a running sum avoids re-sorting the entire window, which reduces repeated O(m log m) work.

What is the best approach for Finding MK Average?

The most efficient approach uses balanced trees (or multisets) to divide the window into three groups: the smallest k elements, the largest k elements, and the middle m−2k elements. The middle group maintains a running sum so the MKAverage can be returned instantly. Insertions and removals take O(log m) time while the query runs in O(1).

Is Finding MK Average asked at Google/Amazon/Meta?

Problems involving sliding windows, streaming statistics, and ordered data structures frequently appear in interviews at companies like Google, Amazon, and Meta. Finding MK Average tests system design thinking combined with balanced tree or heap-based data structures.

What data structure is used in Finding MK Average?

The optimal implementation relies on ordered sets or balanced binary search trees such as TreeMap, multiset, or similar structures. These maintain sorted order while supporting O(log m) insertions and deletions. A queue is also used to track the sliding window of the last m elements.

What is the time complexity of Finding MK Average?

The optimal solution runs in O(log m) time for each stream insertion because balanced tree operations handle ordered insertion and deletion. The MKAverage calculation itself is O(1) by maintaining the sum of the middle partition. Space complexity is O(m) to store the sliding window.

Comments

Now write it yourselfClose this page and solve Finding MK Average in the editor, without looking.
Practise Finding MK Average