실시간 K번째 최고 점수
자바스크립트 코딩테스트 문제로 heap 주제를 연습해보세요. 난이도는 medium이며, 브라우저에서 바로 JavaScript로 풀이를 실행할 수 있습니다.
문제 설명
게임 점수가 시간순으로 담긴 배열 scores와 정수 k가 주어집니다.
각 점수가 하나씩 도착할 때마다 지금까지 나온 점수들 중 K번째로 큰 점수를 구해 배열로 반환하세요.
단, 아직 점수가 k개보다 적다면 K번째 점수가 존재하지 않으므로 그 위치에는 null을 넣어야 합니다.
예를 들어 scores = [50, 30, 70, 60, 90], k = 3이면 각 시점의 K번째 최고 점수는 [null, null, 30, 50, 60]입니다.
제한사항
1 <= scores.length <= 200,0001 <= k <= 200,000- 각 점수는
-1,000,000,000이상1,000,000,000이하의 정수입니다. - 같은 점수가 여러 번 나와도 서로 다른 기록으로 취급합니다.
- 점수가
k개보다 적은 시점에는null을 반환해야 합니다.
예시
- 입력:
[50, 30, 70, 60, 90],k = 3→ 출력:[null, null, 30, 50, 60] - 입력:
[100, 100, 100],k = 2→ 출력:[null, 100, 100] - 입력:
[8, 2, 6, 4],k = 5→ 출력:[null, null, null, null]
첫 번째 예시를 보면:
50만 있을 때는 3번째 최고 점수가 없으므로null50, 30일 때도 아직 2개뿐이므로null50, 30, 70이 되면 정렬 결과는[70, 50, 30]이고 답은30- 그다음
60이 들어오면 상위 3개는70, 60, 50이므로 답은50 - 마지막으로
90이 들어오면 상위 3개는90, 70, 60이므로 답은60
힌트
- 매 시점마다 전체 점수를 다시 정렬하면 너무 느릴 수 있습니다.
- 정말 필요한 것은 상위 k개 점수만 유지하는 것입니다.
- 상위 k개 중 가장 작은 값을 빠르게 꺼낼 수 있으면, 그 값이 바로 현재 K번째 최고 점수입니다.
해설
이 문제를 매번 정렬로 풀면 각 시점마다 지금까지의 점수를 다시 정렬해야 해서 비효율적입니다.
핵심은 상위 k개만 유지하면 충분하다는 점입니다.
예를 들어 k = 3이라면, 지금까지 나온 점수 중 4등 이하 점수들은 현재 3번째 최고 점수에 영향을 줄 수 없습니다. 따라서 우리는 항상 가장 높은 점수 3개만 들고 있으면 됩니다.
이때 가장 잘 맞는 자료구조가 크기가 최대 k인 최소 힙(min-heap) 입니다.
- 힙 크기가 아직
k보다 작으면 새 점수를 그냥 넣습니다. - 힙 크기가 이미
k라면:- 새 점수가 힙의 최솟값보다 크면, 최솟값을 빼고 새 점수를 넣습니다.
- 새 점수가 더 작거나 같으면 상위 k개에 못 들어가므로 버립니다.
- 힙 크기가 정확히
k가 되었을 때, 힙의 루트(최솟값)가 바로 현재 K번째 최고 점수입니다.
왜냐하면 힙 안에는 항상 지금까지 나온 점수 중 가장 큰 k개만 남아 있고, 그중 가장 작은 값이 바로 전체 기준 K등이기 때문입니다.
예를 들어 scores = [50, 30, 70, 60, 90], k = 3이라면:
50→ 힙:[50]→ 아직 3개 미만이므로null30→ 힙:[30, 50]→ 아직 3개 미만이므로null70→ 힙:[30, 50, 70]→ 답3060→ 현재 최솟값30보다 크므로 교체 → 힙:[50, 60, 70]→ 답5090→ 현재 최솟값50보다 크므로 교체 → 힙:[60, 70, 90]→ 답60
이 방식은 각 점수마다 힙 연산이 최대 한 번씩만 일어나므로 시간 복잡도는 O(n log k)이고, 추가 공간은 O(k)입니다.
function solution(scores, k) {
const heap = [];
const result = [];
const push = (value) => {
heap.push(value);
let index = heap.length - 1;
while (index > 0) {
const parent = Math.floor((index - 1) / 2);
if (heap[parent] <= heap[index]) break;
[heap[parent], heap[index]] = [heap[index], heap[parent]];
index = parent;
}
};
const pop = () => {
if (heap.length === 1) return heap.pop();
const top = heap[0];
heap[0] = heap.pop();
let index = 0;
while (true) {
let smallest = index;
const left = index * 2 + 1;
const right = index * 2 + 2;
if (left < heap.length && heap[left] < heap[smallest]) {
smallest = left;
}
if (right < heap.length && heap[right] < heap[smallest]) {
smallest = right;
}
if (smallest === index) break;
[heap[index], heap[smallest]] = [heap[smallest], heap[index]];
index = smallest;
}
return top;
};
for (const score of scores) {
if (heap.length < k) {
push(score);
} else if (score > heap[0]) {
pop();
push(score);
}
result.push(heap.length === k ? heap[0] : null);
}
return result;
}
코드 작성
starter code를 바탕으로 함수를 완성한 뒤 예제 테스트를 실행해보세요.
커스텀 테스트
함수 인자를 JSON 배열 형태로 입력하세요. 예: [3, 5], [[1, 2, 3]]
실행 결과
아직 실행하지 않았습니다.
댓글
문제 풀이 아이디어, 질문, 반례를 자유롭게 나눠보세요.