Finding MK Average

MK-average over a stream — sliding window of last m, three multisets (low/mid/high) for trimmed-mean maintenance.

Hard
Time O(log m) per addElement Space O(m)
LeetCode
8 min read
design multiset sliding-window

Problem#

MKAverage(m, k) maintains a stream. addElement(num) appends to the stream. calculateMKAverage() returns the floor of the mean of the last m elements after removing the smallest k and largest k.

Examples#

  • m=3, k=1; addElement(3); addElement(1); calculateMKAverage()-1 (fewer than m elements).
  • After addElement(10): calculateMKAverage()3 (sorted last 3 = [1,3,10], trim k=1 each end → [3]).

Constraints#

  • 3 <= m <= 10^5, 1 <= k * 2 < m, up to 10^5 calls.

Approach#

Sliding window (deque) of the last m values. Three multisets — low (size k), mid (size m-2k), high (size k) — partition the window by value. Maintain sumMid. On add/evict, insert/remove from the correct multiset and rebalance by swapping boundary elements between adjacent multisets.

Solution#

MKAverage
class MKAverage {
int m, k;
deque<int> q;
multiset<int> low, mid, high;
long long sumMid = 0;
void add(int x) {
if ((int)low.size() < k) low.insert(x);
else if (x <= *low.rbegin()) low.insert(x);
else if ((int)high.size() < k) high.insert(x);
else if (x >= *high.begin()) high.insert(x);
else { mid.insert(x); sumMid += x; }
rebalance();
}
void remove(int x) {
if (low.count(x)) low.erase(low.find(x));
else if (high.count(x)) high.erase(high.find(x));
else { mid.erase(mid.find(x)); sumMid -= x; }
rebalance();
}
void rebalance() {
while ((int)low.size() > k) {
int v = *low.rbegin(); low.erase(prev(low.end()));
mid.insert(v); sumMid += v;
}
while ((int)low.size() < k && !mid.empty()) {
int v = *mid.begin(); mid.erase(mid.begin()); sumMid -= v;
low.insert(v);
}
while ((int)high.size() > k) {
int v = *high.begin(); high.erase(high.begin());
mid.insert(v); sumMid += v;
}
while ((int)high.size() < k && !mid.empty()) {
int v = *mid.rbegin(); mid.erase(prev(mid.end())); sumMid -= v;
high.insert(v);
}
}
public:
MKAverage(int m_, int k_) : m(m_), k(k_) {}
void addElement(int num) {
q.push_back(num);
if ((int)q.size() <= m) {
add(num);
return;
}
int out = q.front(); q.pop_front();
remove(out);
add(num);
}
int calculateMKAverage() {
if ((int)q.size() < m) return -1;
return (int)(sumMid / (long long)(m - 2 * k));
}
};

Editorial#

The three-multiset split is the trick: low holds the smallest k, high holds the largest k, and mid (whose running sum we maintain) is exactly what averages over. Boundary swaps after each insert/remove keep the partition invariant in O(log m).

Complexity#

  • Time: O(log m) per add, O(1) per query.
  • Space: O(m).

Concept revision#

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.