18주차 · 힙과 우선순위 큐
어려움

스트림 중앙값

숫자가 하나씩 들어오는 배열 nums가 주어져요. 값이 하나 들어올 때마다 지금까지 들어온 모든 값의 중앙값을 구해서, 그 중앙값들을 순서대로 담은 배열로 반환해요.

중앙값은 값을 정렬했을 때 가운데 값이에요. 개수가 홀수면 가운데 하나, 짝수면 가운데 두 값의 평균이에요.

매번 전체를 정렬하면 느려요. 작은 절반은 최대 힙, 큰 절반은 최소 힙으로 나눠 개수를 비슷하게 유지하면, 두 힙의 꼭대기만 봐서 중앙값을 바로 구할 수 있어요.

JavaScript는 스타터의 MinHeap에 비교자를 뒤집어 최대 힙을 만들어요. 파이썬은 값에 마이너스를 붙여 heapq로 최대 힙을 흉내 내요.

예시

예시 1
예시 2

제한 사항

  • 1 ≤ nums.length ≤ 100,000
  • -100,000 ≤ nums[i] ≤ 100,000
작은 절반을 담는 최대 힙 low, 큰 절반을 담는 최소 힙 high를 둬요. 새 값이 low의 꼭대기보다 작거나 같으면 low에, 아니면 high에 넣고, 두 힙의 크기 차이가 1을 넘지 않게 옮겨 균형을 맞춰요. low가 더 크면 그 꼭대기가 중앙값, 크기가 같으면 두 꼭대기의 평균이 중앙값이에요.
javascript
class MinHeap {
/** @param {(a: any, b: any) => number} [compare] 음수면 a가 먼저 나옵니다. */
constructor(compare) {
this.items = [];
this.compare = compare ?? ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
}
get size() {
return this.items.length;
}
peek() {
return this.items.length > 0 ? this.items[0] : null;
}
push(value) {
const a = this.items;
a.push(value);
let i = a.length - 1;
while (i > 0) {
const parent = (i - 1) >> 1;
if (this.compare(a[parent], a[i]) <= 0) break;
[a[parent], a[i]] = [a[i], a[parent]];
i = parent;
}
}
pop() {
const a = this.items;
if (a.length === 0) return null;
const top = a[0];
const last = a.pop();
if (a.length > 0) {
a[0] = last;
let i = 0;
for (;;) {
const l = i * 2 + 1;
const r = l + 1;
let small = i;
if (l < a.length && this.compare(a[l], a[small]) < 0) small = l;
if (r < a.length && this.compare(a[r], a[small]) < 0) small = r;
if (small === i) break;
[a[small], a[i]] = [a[i], a[small]];
i = small;
}
}
return top;
}
}
function medianStream(nums) {
const low = new MinHeap((a, b) => b - a);
const high = new MinHeap((a, b) => a - b);
const result = [];
for (const n of nums) {
if (low.size === 0 || n <= low.peek()) low.push(n);
else high.push(n);
if (low.size > high.size + 1) high.push(low.pop());
else if (high.size > low.size) low.push(high.pop());
if (low.size > high.size) result.push(low.peek());
else result.push((low.peek() + high.peek()) / 2);
}
return result;
}
에디터를 불러오고 있어요…
코드를 작성하고를 눌러 예시 테스트를 확인해 보세요.