읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- 큐와 FIFO: head 인덱스로 JavaScript 대기열 만들기큐는 먼저 넣은 값을 먼저 꺼냅니다. 대기열에 넣고 꺼낼 때의 순서를 확인합니다.
- 자료구조 스택 쉽게 이해하기: push pop으로 문제 풀이 감 잡기스택은 마지막에 넣은 값을 먼저 꺼냅니다. 넣고 꺼낼 때 남는 순서를 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 자료구조 개념과 문제 · 자료구조 개념
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
고정 크기 배열로 원형 덱의 양끝 삽입과 삭제를 어떻게 구현하나요?
덱은 앞과 뒤 양쪽에서 값을 넣고 뺄 수 있는 목록입니다. 고정 크기 배열의 끝에서 처음으로 돌아오며 사용하고, 앞 위치와 개수로 빈 상태와 가득 찬 상태를 구분합니다.
적용 조건·범위: 마지막 항목을 지운 뒤 front가 0이 아니어도 오류가 아닙니다. length가 0이면 어떤 슬롯도 활성 원소가 아니며 다음 삽입은 동일한 인덱스 공식을 따릅니다. 빈 덱을 항상 초기 위치로 되돌려야 한다는 조건은 이 계약에 없습니다.
공식 문서 참고: NIST: 양끝 삽입·삭제 자료구조 덱 · NIST: 배열로 구현하는 유한 용량 원형 큐
덱: 양쪽 끝을 쓰고, 빈 칸을 돌아가며 재사용합니다
세 칸의 원형 배열로 덱을 이해합니다. 앞 위치와 개수가 바뀌는 과정을 따라간 뒤 양끝 삽입·삭제 구현으로 연결합니다.

먼저 작은 예제로 원리를 익히고, 마지막에 전체 구현을 펼쳐 보세요. 앞쪽 예제는 각각 독립적으로 브라우저 개발자 도구 Console에서 실행합니다. 같은 이름을 다시 선언했다는 오류가 나오면 새로고침 후 해당 예제를 실행하세요. 출력 뒤에 콘솔이 별도로 보여주는 undefined는 마지막 명령의 반환값일 수 있습니다.

선수 지식: JavaScript 변수·배열·조건문을 먼저 익힙니다. 객체 참조나 함수가 낯설면 아래 단계별 예제부터 시작하고, class 전체 구현은 해당 동작을 손으로 추적한 뒤 읽습니다. 각각의 코드는 독립 실행합니다.
먼저 엘리베이터 대기 줄을 떠올려 봅니다
보통 줄은 뒤로 들어와 앞에서 나갑니다. 그런데 관리자가 긴급 점검 요청을 맨 앞에 넣거나, 잘못 들어온 마지막 요청을 뒤에서 취소해야 한다면 양쪽 끝을 모두 써야 합니다. 이런 줄을 덱이라고 합니다.

여기서는 값 세 개만 들어가는 작은 칸을 사용합니다. 칸 번호는 0, 1, 2이고, 끝 다음은 다시 0입니다. 종이에 원을 그리지 않아도 아래 표처럼 따라갈 수 있습니다.
| 동작 | 0번 칸 | 1번 칸 | 2번 칸 | 읽는 순서 |
|---|---|---|---|---|
| A를 뒤에 넣기 | A | – | – | A |
| B를 뒤에 넣기 | A | B | – | A → B |
| A를 앞에서 빼기 | (사용 안 함) | B | – | B |
| C를 뒤에 넣기 | – | B | C | B → C |
| D를 뒤에 넣기 | D | B | C | B → C → D |
마지막 단계에서 D는 2번 다음인 0번 칸에 들어갑니다. 배열에 보이는 물리 순서는 D, B, C이지만 사람이 읽을 논리 순서는 B, C, D입니다. 원형 덱의 핵심은 값을 매번 옮기지 않고 “어느 칸이 앞인가”를 기억하는 것입니다.
세 칸을 도는 번호부터 확인합니다

const size = 3;
for (let index = 0; index < 5; index += 1) {
console.log(index % size);
}
0 1 2 0 1
%는 나눈 뒤의 나머지를 구합니다. 크기가 3이면 결과는 0, 1, 2 안에서만 움직입니다. 2 다음 계산 결과가 0이 되므로 배열 끝에서 처음으로 돌아갈 수 있습니다. 이 규칙 없이 인덱스를 계속 3, 4로 늘리면 준비한 세 칸 밖에 값을 쓰게 됩니다.
앞 위치를 옮겨도 값은 복사하지 않습니다

const slots = ['A', 'B', 'C'];
let front = 0;
front = (front + 1) % slots.length;
console.log(slots[front]);
console.log(slots.join(', '));
B A, B, C
front는 현재 맨 앞 값의 칸 번호입니다. A를 앞에서 꺼냈다고 생각하면 front만 0에서 1로 옮깁니다. 배열 자체는 여전히 A, B, C로 보이지만 다음 앞 값은 B입니다. 실제 구현은 사용이 끝난 A 칸을 비워 오래된 객체를 계속 붙잡지 않도록 합니다.
순서를 반대로 해 칸을 먼저 비운 뒤 값을 읽으면 반환할 A를 잃습니다. 따라서 “값 저장 → 칸 비우기 → 앞 위치 이동”의 순서를 지켜야 합니다.
뒤에 넣을 칸도 앞에서부터 셉니다

const capacity = 3;
const frontIndex = 1;
const length = 2;
const backIndex = (frontIndex + length) % capacity;
console.log(backIndex);
0
앞이 1번이고 값이 두 개라면 사용 중인 칸은 1번과 2번입니다. 그 다음 칸은 3처럼 보이지만 세 칸만 있으므로 0번으로 돌아갑니다. 여기서 length는 마지막 인덱스가 아니라 현재 들어 있는 값의 개수입니다.
확인 문제: 앞이 2번이고 값이 하나라면 뒤에 넣을 칸은?
계산은 (2 + 1) % 3이고 정답은 0번입니다. 현재 값은 2번에 있고 새 값은 그 다음인 0번에 들어갑니다.
양쪽 동작의 차이를 한 표로 정리합니다

| 동작 | 먼저 해야 할 일 | 바뀌는 정보 |
|---|---|---|
| 뒤에 넣기 | 앞 위치와 현재 개수로 빈 칸 계산 | 값 개수 증가 |
| 앞에서 빼기 | 현재 앞 값을 보관 | 앞 위치 이동, 값 개수 감소 |
| 앞에 넣기 | 앞 위치를 한 칸 뒤로 감기 | 앞 위치, 값 개수 |
| 뒤에서 빼기 | 마지막 값의 칸 계산 | 값 개수 감소 |
비어 있는데 빼려고 하거나 세 칸이 가득 찼는데 더 넣으려는 경우도 먼저 처리해야 합니다. 단순 예제에서는 이 검사를 생략했지만, 이어지는 기존 전체 구현은 빈 상태와 가득 찬 상태, 앞뒤 네 동작, 사용한 칸 정리까지 포함합니다.
바로 실행해 보는 핵심 예제
독자 질문: 고정 크기 버퍼에서 양쪽 끝을 어떻게 순환시키나요?

왜 쓰나요? head와 size를 유지하고 용량으로 나눈 나머지를 인덱스로 씁니다.
아래 바닐라 JavaScript 코드는 독립 실행 예제입니다. 본문에서 배운 개념을 새 데이터로 확인합니다. 다른 예제와 같은 콘솔에 이어 붙이지 말고 새 콘솔 또는 새 .cjs 파일에서 실행하세요.
class Deque {
constructor(capacity) { this.buf = Array(capacity); this.head = 0; this.size = 0; }
pushBack(value) {
if (this.size === this.buf.length) return false;
this.buf[(this.head + this.size++) % this.buf.length] = value; return true;
}
pushFront(value) {
if (this.size === this.buf.length) return false;
this.head = (this.head - 1 + this.buf.length) % this.buf.length;
this.buf[this.head] = value; this.size++; return true;
}
popFront() {
if (!this.size) return undefined;
const value = this.buf[this.head]; this.buf[this.head] = undefined;
this.head = (this.head + 1) % this.buf.length; this.size--; return value;
}
}
const deque = new Deque(3);
deque.pushBack("B"); deque.pushFront("A"); deque.pushBack("C");
console.log(deque.popFront(), deque.popFront(), deque.popFront());
실행 결과와 비용
이번 검토에서는 Node.js v24.18.0에서 위 독립 예제를 실행해 다음 출력과 일치하는지 확인했습니다.
A B C
시간·공간 복잡도: 고정 용량에서 양끝 인덱스 이동은 O(1), 메모리는 O(capacity)입니다. 가득 찼을 때 이 구현은 false를 반환하며 자동 확장은 하지 않습니다.
직접 풀 문제
용량 3에 A·B·C를 채운 뒤 pushBack('D')는 무엇을 반환하나요?
풀이와 기대 결과
false입니다. 덱이 가득 찬 상태와 빈 상태는 size로 구분합니다.
원자료의 다른 학습 순서 (선택)
자료구조 과정 원래 순서의 다음 글: 08 해시 충돌과 문자열 정규화
검증 범위: 위 독립 예제의 출력만 로컬에서 확인했습니다. 이 글은 독립 자료구조 실습입니다. 실제 브라우저 표시와 모든 입력·플랫폼의 실행은 검증하지 않았습니다.
이제 기존 전체 구현을 읽는 순서
전체 코드를 한 번에 외우지 마세요. 먼저 생성자에서 칸 배열, 앞 위치, 값 개수 세 가지를 찾습니다. 다음으로 뒤에 넣기와 앞에서 빼기만 읽어 위 표와 맞춰 봅니다. 그 뒤 앞에 넣기와 뒤에서 빼기를 대칭으로 비교하고, 마지막에 빈 덱과 가득 찬 덱의 처리를 확인하면 됩니다.

아래 정밀 구현에서는 고정 용량 정책과 모든 경계 상황을 다룹니다. 위 코드는 원형 이동 하나씩을 배우기 위한 축소 예제이며, 그대로 완성 덱으로 사용하기 위한 코드는 아닙니다.
관련 코딩테스트 문제로 이어서 연습합니다
이 자료구조의 블로그 연습 문제와 해설에서 배운 동작을 적용해 보세요. 먼저 작은 예시를 직접 처리한 뒤 입력 전체를 다루는 코드로 확장하면 됩니다. 아래 전체 구현은 연결된 문제의 기존 메서드와 반환 형식을 유지합니다.
전체 구현 · 상세 설명 · 예외와 성능 분석 펼치기
여기부터는 필요한 기능을 골라 읽는 참고 영역입니다. 새로운 파일로 전체 코드를 실행할 때는 아래 Node.js 실행 안내를 따르세요. 앞쪽의 작은 브라우저 실습과 전체 파일을 한 콘솔에 이어 붙이지 마세요. 기능별 입력 조건과 반환 형식은 아래 설명을 기준으로 합니다.
왜 덱이 필요한가
일반 큐는 뒤로 넣고 앞으로 꺼냅니다. 편집 기록의 최근 항목을 되돌리려면 뒤에서도 꺼낼 수 있어야 합니다. 덱(deque, double-ended queue)은 이 두 방향을 함께 지원하는 추상 자료형입니다. 여기서 원형 배열은 그 계약을 구현하는 한 방법이지, 덱 자체의 정의는 아닙니다.

JavaScript 배열의 앞에 unshift로 삽입하거나 shift로 삭제하면 인덱스를 앞으로 또는 뒤로 당기는 비용을 고려해야 합니다. 원형 덱은 값의 자리를 옮기지 않고 “논리적 첫 항목이 어디인가”만 바꿉니다. 반대로 중간 위치 삽입이나 정렬이 주작업이라면 이 구조가 특별히 유리하지 않습니다.
저장 계약: front와 length가 의미하는 것
capacity는 1 이상의 정수이며 실행 가능한 메모리 크기 안에서 정합니다. data는 고정 개수의 슬롯, front는 첫 항목의 물리 위치, length는 현재 원소 수입니다. 항상 0 ≤ length ≤ capacity, 0 ≤ front < capacity를 유지하며 논리 위치 i의 실제 위치는 (front + i) % capacity입니다.

삽입은 성공하면 true, 가득 차면 false를 반환하고 기존 값을 바꾸지 않습니다. 오래된 기록을 자동으로 버릴지는 덱을 사용하는 프로그램이 결정합니다. 이런 분리는 실수로 작업을 잃으면 안 되는 큐에도 같은 구현을 재사용하게 해 줍니다.
삭제 결과는 빈 덱이면 {done:true}, 값이 있으면 {done:false,value}입니다. undefined 자체도 저장할 수 있으므로 undefined만 반환하면 “빈 덱”과 “undefined 저장”을 구별하지 못합니다. 이 예제는 별도의 done 표시로 구별합니다. 객체를 저장하면 복사가 아니라 참조가 보관됩니다.
전체 코드와 실행
아래 전체를 deque.cjs로 저장하고 터미널에서 node ./deque.cjs를 실행합니다. Node.js CommonJS 파일 하나이며 npm 패키지나 브라우저가 필요하지 않습니다. module.exports는 테스트용이고 맨 아래 직접 실행 블록이 실제 호출과 출력을 담당합니다.
'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;
}
}
module.exports = { CircularDeque };
if (require.main === module) {
const deque = new CircularDeque(3);
deque.pushBack('A');
deque.pushBack('B');
const removed = deque.popFront();
deque.pushBack('C');
deque.pushFront('D');
const full = deque.pushBack('E');
console.log(JSON.stringify({ removed, full, values: deque.toArray(), front: deque.front }));
}
실행 출력
{"removed":{"done":false,"value":"A"},"full":false,"values":["D","B","C"],"front":0}
각 메서드가 불변식을 지키는 방법
pushBack은 length개 뒤의 빈 슬롯을 선택합니다. length를 먼저 늘리면 한 칸 뒤에 써 버리므로 값을 기록한 뒤 증가시킵니다. full 검사를 가장 먼저 하는 이유는 덮어쓰기를 막기 위해서입니다. 읽을 때도 물리 배열 순서가 아니라 front에서 시작하는 순서를 사용합니다.

pushFront는 첫 위치를 한 칸 뒤로 이동시킨 다음 값을 기록합니다. JavaScript의 %는 수학적 양의 모듈러와 달라 -1 % 3이 -1입니다. 따라서 front – 1에 capacity를 더해 음수를 피합니다. front가 0인 경우에도 새 위치는 capacity – 1이 됩니다.
popFront는 현재 front의 값을 먼저 저장하고 슬롯을 비운 뒤 front를 한 칸 전진시킵니다. popBack은 마지막 논리 위치 length – 1을 계산하고 front는 유지합니다. 사용한 슬롯에 undefined를 대입하면 큰 객체에 대한 불필요한 참조를 덱이 계속 붙들지 않습니다.
마지막 항목을 지운 뒤 front가 0이 아니어도 오류가 아닙니다. length가 0이면 어떤 슬롯도 활성 원소가 아니며 다음 삽입은 동일한 인덱스 공식을 따릅니다. 빈 덱을 항상 초기 위치로 되돌려야 한다는 조건은 이 계약에 없습니다.
용량 3에서의 동작 추적

| 연산 | front | length | 논리 순서 | 결과 |
|---|---|---|---|---|
| 시작 | 0 | 0 | [] | — |
| pushBack(A), pushBack(B) | 0 | 2 | [A,B] | 각 true |
| popFront() | 1 | 1 | [B] | A |
| pushBack(C) | 1 | 2 | [B,C] | true |
| pushFront(D) | 0 | 3 | [D,B,C] | true |
| pushBack(E) | 0 | 3 | [D,B,C] | false |
| popBack() | 0 | 2 | [D,B] | C |
| popFront() 두 번 | 2 | 0 | [] | D, B |
| popBack() | 2 | 0 | [] | done:true |
배열을 실제로 순환시키는 경우도 확인해 보세요. 위 표의 빈 상태에서 X,Y를 뒤에 넣으면 슬롯 2와 0에 저장되지만 toArray()는 [X,Y]를 반환합니다. 물리 배열을 그대로 출력하는 것과 논리 덱을 출력하는 것은 다릅니다.
복잡도와 적용 한계

| 작업 | 시간 | 추가 공간 |
|---|---|---|
| 생성 | O(K) | O(K) 슬롯 |
| 양끝 삽입·삭제 | 최악 O(1) | O(1) |
| toArray | O(n) | 반환 배열 O(n) |
K는 용량, n은 현재 항목 수입니다. 위 비용은 배열 인덱스 접근을 상수 시간으로 보는 일반적인 모델이며 값 자체를 깊은 복사하지 않습니다. 고정 용량이므로 이 구현에는 자동 확장에 따른 가끔 O(n)이 되는 삽입이 없습니다. 대신 K개를 넘으면 반드시 실패합니다.
크기 상한을 모르는 프로그램은 동적 원형 배열이나 연결 덱을 고려할 수 있습니다. 메모리가 제한된 기록 버퍼라면 고정 용량과 명시적 퇴출 정책이 오히려 계약을 선명하게 합니다. 전체 기록을 매번 배열로 반환하면 덱 연산이 빨라도 출력 복사 비용이 더 커질 수 있습니다.
경계 확인과 연습
용량 1에서 삽입·실패·삭제를 반복하고, 빈 상태의 양쪽 삭제, null과 undefined 저장, 물리 끝을 여러 번 넘는 삽입을 확인하세요. 성공한 삽입만 length를 늘리고 성공한 삭제만 줄인다는 조건으로 대부분의 인덱스 오류를 찾을 수 있습니다.

함께 연습하고 확인할 자료
Design Circular Deque: 양끝 삽입·삭제 API를 직접 설계하는 외부 연습입니다. 반환 계약은 이 글과 다르므로 공식 문제의 요구를 별도로 읽으세요.
관련 개념 참고: 위 공식 문제의 API 계약을 함께 살펴보세요. 공식 자료와 문제 페이지는 2026년 9월 10일 확인했습니다. 외부 문제의 지문·예시를 복제하지 않고 이 글의 예제와 창작 문제를 별도로 구성했습니다.
원형 배열 덱과 연결 덱은 같은 네 연산을 다르게 저장합니다
덱의 핵심은 앞·뒤 양쪽에서 넣고 뺄 수 있다는 규칙입니다. 고정 용량 원형 배열은 front와 length로 논리 위치를 실제 칸에 대응시키며, 이중 연결 덱은 head·tail과 prev·next로 양끝을 이어 둡니다. 세 칸에 A·B·C를 넣고 앞의 A를 꺼낸 뒤 D를 뒤에 넣으면, 두 구현의 논리 순서는 B·C·D로 같습니다. 배열 안의 물리적 칸 순서가 B·C·D와 달라도 front부터 읽으면 같은 결과여야 합니다.

현재 원형 덱은 가득 차면 삽입이 false이고 기존 값을 덮어쓰지 않습니다. 삭제는 {done:true} 또는 {done:false,value}로 구분하여 undefined도 데이터로 저장합니다. 연결 덱을 비교 구현하더라도 이 계약을 유지해야 호출자를 그대로 사용할 수 있습니다. 연결 방식은 고정 배열의 빈 칸을 미리 확보하지 않는 대신 노드별 링크를 저장합니다. 양끝에서 노드 참조를 직접 고치면 네 연산이 O(1)이지만, 단방향 리스트로 뒤 삭제까지 같은 비용이라고 가정해서는 안 됩니다. clear·마지막 삭제·재삽입 뒤 양끝 참조가 일치하는지도 함께 확인합니다.
첨부 test_deque.mjs의 양끝 시연과 원형 덱의 계약을 나눠 읽습니다
dev.zip의 dev/test_deque.mjs는 이름에 test가 있지만 assert로 통과·실패를 판정하는 자동 테스트가 아닙니다. console.log와 출력용 메서드로 결과를 관찰하는 시연 파일입니다. 아래에서 호출 뒤 남은 상태와 함수가 반환하는 값을 따로 읽어 보세요. 원본은 상대 모듈을 가져오므로 코드 한 조각만 콘솔에 붙이면 독립 실행되지 않습니다.

이 시연은 고정 용량 원형 배열이 아니라 DoublyLinkedList를 감싼 연결 덱을 사용합니다. 처음과 끝에 각각 값을 넣고 빼는 네 메서드를 볼 수 있지만, 두 구간 모두 넣은 쪽에서 다시 꺼내는 순서입니다. 앞에 넣고 뒤에서 빼는 조합, 가득 참, 이미 빈 상태에서 삭제하는 상황을 이 파일이 검사하는 것은 아닙니다.
| 호출 구간 | printAll의 기대 결과 | 상태 확인 |
|---|---|---|
| 처음 isEmpty(), addFirst(1)부터 addFirst(5) | [5, 4, 3, 2, 1] | 초기 isEmpty: true, 삽입 후 false / count=5 |
| removeFirst()를 다섯 번, 매번 printAll | [4, 3, 2, 1] → [3, 2, 1] → [2, 1] → [1] → [] | 반환 Node.data는 5,4,3,2,1이지만 원본은 반환값을 출력하지 않습니다. |
| 첫 구간 끝 isEmpty() | 출력 문자열 isEmpty: true | count=0, head=null, tail=null |
| addLast(1)부터 addLast(5) | [1, 2, 3, 4, 5] | 삽입 후 isEmpty: false / count=5 |
| removeLast()를 다섯 번, 매번 printAll | [1, 2, 3, 4] → [1, 2, 3] → [1, 2] → [1] → [] | 반환 Node.data는 다시 5,4,3,2,1입니다. |
| 마지막 isEmpty() | 출력 문자열 isEmpty: true | 두 번째 구간도 count=0, head=null, tail=null |
양쪽 구간의 출력 순서가 같다고 앞과 뒤의 의미까지 같은 것은 아닙니다. 첫 구간은 head를 바꾸며, 둘째 구간은 tail을 prev로 옮깁니다. 한 개만 남았을 때의 마지막 삭제는 두 참조를 모두 null로 만들어야 합니다. 중간의 console.log("\n")는 시연을 나누는 빈 줄이며 덱 데이터가 아닙니다.
| 구현 | 삽입 | 삭제·빈 상태 |
|---|---|---|
| 첨부 Deque | addFirst/addLast, 반환 undefined입니다. 용량 매개변수·가득 참 정책이 없습니다. | removeFirst/removeLast는 Node입니다. 이미 비어 있으면 내부 삭제의 예외가 그대로 나옵니다. |
| 본문 완성 CircularDeque | pushFront/pushBack, 성공 true·가득 차면 false입니다. | popFront/popBack은 {done:false,value}, 비었으면 {done:true}입니다. |
| 본문의 간단 class Deque 예제 | pushFront/pushBack으로 넣습니다. | 간단 popFront는 저장한 값, 비었으면 undefined입니다. 완성 버전과도 계약을 구분합니다. |
따라서 원본의 다섯 번째 정상 삭제 뒤에 여섯 번째 삭제를 추가하면 빈 결과 객체가 아니라 예외를 예상해야 합니다. 원본의 삽입 반환 undefined를 false로 판단해서도 안 됩니다. 연결 덱은 고정 배열 칸을 미리 확보하지 않는 대신 노드마다 next·prev를 저장하며 이 구현의 양끝 연산은 O(1)입니다. 원형 배열의 물리 칸 순환·용량 초과 검사는 별도로 수행해야 합니다.
자동 검증으로 바꾸려면 각 단계의 assert에 count와 논리 순서를 함께 적고, 반환 Node.data와 head.prev=null·tail.next=null도 분리해 확인합니다. 추가 조합 addFirst(1), addLast(2), removeLast(), removeFirst()의 data는 2,1이며 끝에는 양끝이 null입니다. 이는 위 원본 파일의 두 시연을 넘어서는 별도 경계 확인입니다.
자기 확인: 원본에서 삽입 뒤 undefined가 보이면 넣기에 실패한 것인가요?
아닙니다. addFirst/addLast는 명시적인 반환값이 없습니다. 남은 목록과 count로 삽입을 확인합니다. 가득 참을 false로 알려 주는 계약은 현재 글의 CircularDeque에 해당합니다.
직접 실습: 연산 비용으로 구조를 설명합니다
실습 주제: JavaScript 원형 덱 구현: 양끝 삽입·삭제와 고정 용량 버퍼

- 본문 구현에서 저장되는 값과 연결 관계를 그림으로 적습니다.
- 조회·삽입·삭제 중 이 구조가 가장 자주 수행할 연산을 고릅니다.
- 연산 전후에도 유지되어야 하는 규칙을 한 문장으로 적습니다.
- 배열이나 Map 같은 다른 구조로 바꿨을 때 시간·공간 비용을 비교합니다.
풀이 기준과 확인 결과
메서드 이름만 외우지 말고 한 번의 연산에서 어떤 값과 연결이 바뀌는지 추적하세요. 빈 구조, 원소 한 개, 중복값, 연속 삽입·삭제를 실행했을 때 본문이 설명한 불변식이 유지되면 성공입니다.
테스트 체크리스트

- 빈 구조에 대한 조회·삭제 처리
- 첫 원소와 마지막 원소 변경
- 중복값 또는 동일 우선순위 처리
- 입력 크기가 커졌을 때 예상 복잡도 유지
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 삽입·제거 전후의 상태를 그리고, 내장 도구를 쓰는 풀이와 직접 구현의 목적을 구분하세요.
다음 추천 · 이 개념으로 풀 문제: 원형 덱 연습: 최근 기록 창과 되돌리기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 23개 · 필수 23개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.