18주차 · 힙과 우선순위 큐
보통

K개 정렬 리스트 병합

각각 오름차순으로 정렬된 정수 리스트들이 배열 lists로 주어져요. 이들을 하나의 오름차순 리스트로 합쳐 반환해요.

전부 이어 붙여 다시 정렬해도 되지만, 각 리스트가 이미 정렬돼 있다는 점을 살리면 더 좋아요. 각 리스트의 맨 앞 값만 힙에 넣고 가장 작은 것을 꺼내 결과에 담은 뒤, 그 값이 온 리스트의 다음 값을 힙에 넣어요. 이러면 힙 크기가 리스트 개수만큼만 유지돼요.

JavaScript는 스타터에 딸려 온 MinHeap을 써요. 파이썬은 heapq를 써요.

예시

예시 1
예시 2

제한 사항

  • 0 ≤ lists.length ≤ 10,000
  • 각 리스트는 오름차순으로 정렬돼 있어요.
  • 모든 값의 개수 합은 100,000 이하예요.
  • -10^9 ≤ 값 ≤ 10^9
각 리스트의 첫 값을 [값, 리스트번호, 원소인덱스] 형태로 힙에 넣어요(비교 기준은 값). 힙에서 가장 작은 것을 꺼내 결과에 담고, 그 리스트에 다음 값이 있으면 그 값을 힙에 넣어요. 힙이 빌 때까지 반복해요.
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 mergeKLists(lists) {
const heap = new MinHeap((a, b) => a[0] - b[0]);
for (let i = 0; i < lists.length; i++) {
if (lists[i].length > 0) heap.push([lists[i][0], i, 0]);
}
const out = [];
while (heap.size > 0) {
const [value, i, j] = heap.pop();
out.push(value);
if (j + 1 < lists[i].length) heap.push([lists[i][j + 1], i, j + 1]);
}
return out;
}
에디터를 불러오고 있어요…
코드를 작성하고를 눌러 예시 테스트를 확인해 보세요.