
핵심 요약
우선순위가 낮은 숫자인 작업부터 실행하되 동률은 입력 순서를 지킵니다. 작업 ID의 중복과 정렬 안정성을 구분하는 독립 창작 문제입니다.
독립 창작 문제
이 문제의 상황·입출력 계약·예시는 이 글을 위해 직접 구성했습니다. 공식 문제를 번역하거나 복제한 지문이 아닙니다. 구현 개념은 JavaScript 최소 힙 구현: 우선순위 큐의 push·pop과 비교 함수에서 먼저 확인할 수 있습니다.
실행 대기 중인 작업 목록 tasks가 주어집니다. priority가 작은 작업부터 처리한 ID 목록을 반환하세요. priority가 같으면 입력 배열에서 먼저 등장한 작업을 먼저 처리합니다. 작업 ID가 같아도 별개 요청이므로 합치거나 제거하지 않습니다.
이 문제는 실행 중에 새 작업이 도착하는 시뮬레이션이 아닙니다. 모든 작업을 먼저 넣은 뒤 하나씩 꺼냅니다. 일괄 입력만 놓고 보면 정렬로도 풀 수 있지만, 여기서는 이후 삽입과 추출이 섞이는 상황에도 사용할 수 있는 최소 힙의 비교 계약을 연습합니다.
입력·출력과 제약
표준 입력은 {tasks} JSON 객체입니다. tasks는 길이 Q가 0~20000인 배열이며 각 작업은 {id,priority}입니다. id는 길이 1~100의 문자열, priority는 -1000000~1000000의 정수입니다. 입력은 이 형식을 만족합니다. 표준 출력은 작업이 처리되는 순서대로 ID를 담은 JSON 배열입니다.
priority가 음수여도 더 작은 숫자가 먼저라는 규칙은 같습니다. 출력에는 모든 작업이 정확히 한 번씩 나타나므로 중복 ID도 그대로 반복됩니다. 입력 작업 객체를 수정하지 않고 새 객체에 내부용 order를 추가합니다.
입력 예시
{"tasks":[{"id":"메일","priority":2},{"id":"백업","priority":-1},{"id":"점검","priority":2},{"id":"메일","priority":0}]}
출력 예시
["백업","메일","메일","점검"]
실행 방법
Node.js CommonJS 환경에서 정답의 전체 코드를 heap-problem.cjs로 저장합니다. 입력 예시를 UTF-8 input.json 파일로 저장한 뒤 Windows PowerShell에서는 아래 명령을 실행하세요. 내장 node:fs 외에 다른 파일이나 패키지가 필요하지 않습니다.
Get-Content -Raw -Encoding UTF8 ./input.json | node ./heap-problem.cjs
macOS·Linux의 Bash에서는 아래 입력 리다이렉션을 사용할 수 있습니다. 이 < 문법은 PowerShell 명령이 아닙니다.
node ./heap-problem.cjs < input.json
힌트
비교의 첫 기준을 priority, 두 번째 기준을 입력 인덱스 order로 두세요. ID의 사전순은 요구된 기준이 아닙니다. 힙이 동률을 알아서 안정적으로 처리할 것이라고 가정하지 않아야 합니다.
정답 코드와 해설 펼치기
한 파일로 실행하는 전체 정답
'use strict';
class MinHeap {
constructor(compare = (a, b) => a - b) {
if (typeof compare !== 'function') throw new TypeError('compare must be a function');
this.compare = compare;
this.data = [];
}
get size() {
return this.data.length;
}
peek() {
return this.data[0];
}
push(value) {
if (value === undefined) throw new TypeError('undefined is reserved for an empty heap');
let hole = this.data.length;
this.data.push(value);
while (hole > 0) {
const parent = Math.floor((hole - 1) / 2);
if (this.compare(this.data[parent], value) <= 0) break;
this.data[hole] = this.data[parent];
hole = parent;
}
this.data[hole] = value;
}
pop() {
if (this.data.length === 0) return undefined;
const minimum = this.data[0];
const last = this.data.pop();
if (this.data.length === 0) return minimum;
let hole = 0;
while (hole * 2 + 1 < this.data.length) {
let child = hole * 2 + 1;
const right = child + 1;
if (right < this.data.length &&
this.compare(this.data[right], this.data[child]) < 0) {
child = right;
}
if (this.compare(last, this.data[child]) <= 0) break;
this.data[hole] = this.data[child];
hole = child;
}
this.data[hole] = last;
return minimum;
}
}
function solve({ tasks }) {
const heap = new MinHeap((a, b) => a.priority - b.priority || a.order - b.order);
tasks.forEach((task, order) => heap.push({ ...task, order }));
const result = [];
while (heap.size > 0) result.push(heap.pop().id);
return result;
}
module.exports = { solve };
if (require.main === module) {
const fs = require('node:fs');
const input = JSON.parse(fs.readFileSync(0, 'utf8').replace(/^\uFEFF/, ''));
process.stdout.write(JSON.stringify(solve(input)) + '\n');
}
정답 해설: 안정성을 비교 키로 바꿉니다
tasks.forEach의 인덱스는 입력 순서이며 모든 작업에서 다릅니다. (priority,order)를 사전식으로 비교하면 서로 다른 작업의 순서가 하나로 정해집니다. priority 차이가 0일 때만 order 차이를 사용하는 || 표현으로 이 계약을 구현합니다. 문제의 숫자 범위에서는 차이 연산도 안전한 정수입니다.
최소 힙의 부모는 항상 자식보다 앞선 작업입니다. 모든 작업을 push한 뒤 루트부터 pop하면 현재 남은 작업 중 가장 작은 비교 키가 차례로 나옵니다. 같은 우선순위라도 order가 작을수록 앞서므로 힙 내부 교체가 입력 순서를 바꿀 수 없습니다.
| 작업 ID | priority | order | 처리 순위 |
|---|---|---|---|
| 메일(첫 번째) | 2 | 0 | 3 |
| 백업 | -1 | 1 | 1 |
| 점검 | 2 | 2 | 4 |
| 메일(두 번째) | 0 | 3 | 2 |
두 메일 요청은 ID가 같아도 order가 다르고 priority도 다릅니다. 결과에서 문자열만 보면 두 요청을 구분하지 못하지만 각각 한 번씩 실행된 것입니다. 반환 결과를 Set으로 만들면 요청 하나를 잃게 됩니다. 힙에 원본 객체를 그대로 넣고 order를 덧쓰지 않는 이유도 입력에 부수 효과를 만들지 않기 위해서입니다.
복잡도와 대안
작업 Q개를 하나씩 push하고 Q번 pop하므로 시간은 O(Q log(Q+1))입니다. 비교는 작은 정수 두 개만 사용해 O(1)입니다. 입력 문자열 총길이를 C라고 하면 입력·힙 객체·출력 배열을 포함한 공간은 O(Q+C)입니다. 문자열은 참조로 사용하므로 매 비교마다 ID 전체를 읽지 않습니다.
모든 작업이 처음부터 있고 전체 결과만 필요하면 (priority,입력 순번)으로 한 번 정렬하는 해법도 같은 점근 시간이며 더 짧습니다. 힙 구현의 학습 가치는 동적 우선순위 큐에 있습니다. 아래 코드에는 실행 중 우선순위 변경이나 임의 작업 취소 기능이 없으며 그런 기능을 있다고 가정하지 않습니다.
직접 확인할 경계
빈 tasks, 작업 하나, 모든 priority가 같은 목록, 음수 priority, 중복 ID를 확인하세요. 각 작업에 입력 인덱스를 붙여 배열 정렬한 결과를 기준으로 무작위 테스트하면 힙이 우선순위와 동률 계약을 동시에 지키는지 검증할 수 있습니다.
함께 연습하고 확인할 자료
Kth Largest Element in a Stream: 스트림에서 K번째 큰 값을 유지하는 외부 연습입니다. 이 글의 모든 작업 출력 문제와는 목표가 다릅니다.
관련 개념 참고: Princeton Algorithms — Priority Queues 공식 자료와 문제 페이지는 2026년 9월 10일 확인했습니다. 외부 문제의 지문·예시를 복제하지 않고 이 글의 예제와 창작 문제를 별도로 구성했습니다.
풀이 전에 확인할 순서
- 입력값과 출력값을 한 문장으로 다시 적습니다.
- 반복할 대상과 비교·저장할 값을 정합니다.
- 필요한 자료구조와 시간복잡도를 예상합니다.
- 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.
테스트 확인
- 가능한 가장 작은 입력
- 같은 값이나 문자가 반복되는 입력
- 정답이 처음 또는 마지막 위치에서 결정되는 입력
- 입력 제한에 가까운 경우의 실행 시간
확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.
이 글이 도움이 되었나요?
코딩테스트 JavaScript 학습 순서
필수 49개 · 전체 49개
읽음 기록 관리
전체 과정 목차 (49개)
- 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
- 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
- 필수 학습 · 삼각형 판별하기 JavaScript 풀이
- 필수 학습 · 연필 개수 JavaScript 풀이
- 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
- 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
- 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
- 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
- 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
- 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
- 필수 학습 · 대문자 찾기 JavaScript 풀이
- 필수 학습 · 대문자로 통일 JavaScript 풀이
- 필수 학습 · 대소문자 변환 JavaScript 풀이
- 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
- 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
- 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
- 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
- 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
- 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
- 필수 학습 · 가장 긴 문자열 JavaScript 풀이
- 필수 학습 · 가운데 문자 출력 JavaScript 풀이
- 필수 학습 · 중복문자제거 JavaScript 풀이
- 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
- 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
- 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
- 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
- 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
- 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
- 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
- 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
- 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
- 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
- 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
- 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
- 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
- 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러 현재 글
- 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
- 필수 학습 · 중복단어제거 JavaScript 풀이
- 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
- 필수 학습 · 큰 수 출력하기 JavaScript 풀이
- 필수 학습 · 보이는 학생 JavaScript 풀이
- 필수 학습 · 가위바위보 JavaScript 풀이
- 필수 학습 · 점수계산 JavaScript 풀이
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.