
핵심 요약
고정 크기의 최근 기록 창을 유지하면서 가장 최근 기록을 되돌립니다. 덱의 삽입 실패와 기록 퇴출을 구분하고 매 이벤트 뒤의 논리 순서를 반환하는 독립 창작 문제입니다.
독립 창작 문제
이 문제의 상황·입출력 계약·예시는 이 글을 위해 직접 구성했습니다. 공식 문제를 번역하거나 복제한 지문이 아닙니다. 구현 개념은 JavaScript 원형 덱 구현: 양끝 삽입·삭제와 고정 용량 버퍼에서 먼저 확인할 수 있습니다.
기록 창은 처음에 비어 있고 최대 capacity개의 값을 오래된 순서부터 저장합니다. add 이벤트가 오면 뒤에 값을 넣습니다. 이미 가득 찼다면 가장 오래된 값 하나를 앞에서 제거한 뒤 새 값을 넣습니다. undo 이벤트는 현재 창의 가장 최근 값 하나만 제거하며 빈 창에서는 아무 일도 하지 않습니다.
각 이벤트를 처리한 직후의 창을 배열로 복사해 결과에 추가하세요. 퇴출되었거나 undo로 지운 기록은 복구하지 않습니다. undo가 이전 add 작업의 전체 상태를 되돌리는 명령이 아니라 현재 창의 마지막 항목을 삭제하는 명령이라는 점에 유의하세요.
입력·출력과 제약
표준 입력은 하나의 JSON 객체 {capacity,events}입니다. capacity는 1~1000의 정수, events는 길이 Q가 0~1000인 배열입니다. 각 이벤트는 {type:”add”,value} 또는 {type:”undo”}이며 value는 null, 불리언, 안전 정수 또는 길이 100 이하 문자열입니다. undefined는 JSON 값이 아니므로 문제 입력에는 없습니다. 입력은 이 형식을 만족한다고 가정합니다.
표준 출력은 Q개의 스냅숏을 가진 JSON 배열입니다. 각 스냅숏의 항목은 오래된 것부터 최근 것 순서입니다. capacity를 넘지 않아야 하며 같은 값의 중복도 별개 기록으로 보관합니다. 반환한 스냅숏은 나중 이벤트로 변경되면 안 됩니다.
입력 예시
{"capacity":2,"events":[{"type":"add","value":"봄"},{"type":"add","value":"여름"},{"type":"add","value":"가을"},{"type":"undo"},{"type":"undo"},{"type":"undo"}]}
출력 예시
[["봄"],["봄","여름"],["여름","가을"],["여름"],[],[]]
실행 방법
Node.js CommonJS 환경에서 정답의 전체 코드를 deque-problem.cjs로 저장합니다. 입력 예시를 UTF-8 input.json 파일로 저장한 뒤 Windows PowerShell에서는 아래 명령을 실행하세요. 내장 node:fs 외에 다른 파일이나 패키지가 필요하지 않습니다.
Get-Content -Raw -Encoding UTF8 ./input.json | node ./deque-problem.cjs
macOS·Linux의 Bash에서는 아래 입력 리다이렉션을 사용할 수 있습니다. 이 < 문법은 PowerShell 명령이 아닙니다.
node ./deque-problem.cjs < input.json
힌트
덱 자체를 “가득 차면 자동 삭제”로 바꾸지 않아도 됩니다. pushBack 실패 여부를 보고 popFront와 재삽입을 수행하세요. 매번 같은 내부 배열 참조를 결과에 넣지 말고 논리 순서를 복사해야 이전 상태가 유지됩니다.
정답 코드와 해설 펼치기
한 파일로 실행하는 전체 정답
'use strict';
class CircularDeque {
constructor(capacity) {
if (!Number.isInteger(capacity) || capacity < 1) {
throw new RangeError('capacity must be a positive integer');
}
this.data = new Array(capacity);
this.capacity = capacity;
this.front = 0;
this.length = 0;
}
pushFront(value) {
if (this.length === this.capacity) return false;
this.front = (this.front - 1 + this.capacity) % this.capacity;
this.data[this.front] = value;
this.length += 1;
return true;
}
pushBack(value) {
if (this.length === this.capacity) return false;
const index = (this.front + this.length) % this.capacity;
this.data[index] = value;
this.length += 1;
return true;
}
popFront() {
if (this.length === 0) return { done: true };
const value = this.data[this.front];
this.data[this.front] = undefined;
this.front = (this.front + 1) % this.capacity;
this.length -= 1;
return { done: false, value };
}
popBack() {
if (this.length === 0) return { done: true };
const index = (this.front + this.length - 1) % this.capacity;
const value = this.data[index];
this.data[index] = undefined;
this.length -= 1;
return { done: false, value };
}
toArray() {
const result = [];
for (let offset = 0; offset < this.length; offset += 1) {
result.push(this.data[(this.front + offset) % this.capacity]);
}
return result;
}
}
function solve({ capacity, events }) {
const deque = new CircularDeque(capacity);
const snapshots = [];
for (const event of events) {
if (event.type === 'add') {
if (!deque.pushBack(event.value)) {
deque.popFront();
deque.pushBack(event.value);
}
} else if (event.type === 'undo') {
deque.popBack();
} else {
throw new TypeError('unknown event type');
}
snapshots.push(deque.toArray());
}
return snapshots;
}
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');
}
정답 해설: 자료구조와 정책을 분리합니다
CircularDeque는 가득 찬 삽입에 false만 돌려줍니다. solve는 이 실패를 기록 창의 퇴출 조건으로 해석해 앞의 한 항목을 제거합니다. 용량이 1 이상이므로 한 번 제거한 다음 재삽입은 반드시 성공합니다. 이 논리는 버퍼 정책이며 덱 메서드의 보편적인 동작은 아닙니다.
undo는 popBack의 반환값이 done:true여도 그대로 진행합니다. 빈 상태의 undo가 유효한 명령이기 때문입니다. 이벤트마다 toArray가 새 배열을 만들어 snapshots에 넣으므로 그 뒤 덱의 슬롯이 재사용되어도 지난 스냅숏은 바뀌지 않습니다. 이 문제의 값은 원시값으로 제한해 깊은 객체 복사 문제도 피합니다.
| 이벤트 | 처리 | 스냅숏 |
|---|---|---|
| add 봄 | 뒤에 추가 | [봄] |
| add 여름 | 뒤에 추가 | [봄,여름] |
| add 가을 | 봄 퇴출 후 추가 | [여름,가을] |
| undo | 가을 삭제 | [여름] |
| undo | 여름 삭제 | [] |
| undo | 빈 상태 유지 | [] |
불변식은 이벤트 i개를 처리한 뒤 덱이 “지금 창에 남은 기록”만 순서대로 보관하고 길이는 capacity 이하라는 것입니다. add가 남길 수 있는 가장 오래된 기록만 하나 버리고 undo가 마지막 하나만 지우므로 각 분기가 이 조건을 유지합니다. 출력은 그 상태를 그대로 복사합니다.
복잡도
K는 capacity, Q는 이벤트 수, S는 모든 스냅숏 길이의 합입니다. 덱 갱신은 이벤트마다 O(1)이지만 출력 복사는 O(S)이므로 총 시간은 O(Q+K+S), 최악 O(K+QK)입니다. 덱만 보면 O(K) 공간이지만 입력 JSON과 결과까지 보관하는 전체 프로그램은 O(K+Q+S+C) 공간입니다. C는 입력 문자열 총길이이며 최악의 스냅숏 원소 수는 QK입니다.
직접 확인할 경계
capacity=1에서 연속 add, 첫 이벤트 undo, events=[], 같은 값 두 번, null 저장, 물리 배열 끝을 여러 번 지나는 이벤트를 확인하세요. 최종 창만 맞는지 비교하지 말고 모든 중간 스냅숏을 배열 기반 기준 구현과 비교해야 undo 이전의 출력 복사 오류를 찾을 수 있습니다.
함께 연습하고 확인할 자료
Design Circular Deque: 양끝 삽입·삭제 API를 직접 설계하는 외부 연습입니다. 반환 계약은 이 글과 다르므로 공식 문제의 요구를 별도로 읽으세요.
관련 개념 참고: 위 공식 문제의 API 계약을 함께 살펴보세요. 공식 자료와 문제 페이지는 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의 새 글을 확인할 수 있습니다.