덱 (Deque)
정의
Deque (덱, double-ended queue) 는 양쪽 끝에서 O(1) push/pop 이 가능한 선형 자료구조. 큐 + 스택의 일반화.
내부 구현은 대개 chunked array (C++ STL std::deque) 또는 doubly-linked list. 랜덤 접근은 O(1) (STL 기준), 중간 삽입/삭제는 O(N).
문제 상황과 동기
큐는 front 에서만 pop, rear 에서만 push. 스택은 한쪽 끝만 push/pop. 양쪽 모두 가능하면?
- naive:
vector를 쓰되, 맨 앞 삽입은 O(N) shift. 실시간 불가. - deque: push_front, push_back, pop_front, pop_back 모두 O(1). 슬라이딩 윈도우 최대/최소 같은 응용 가능.
핵심 통찰: monotonic deque (단조 덱) 로 윈도우 내 min/max 를 O(1) 로 유지. 각 원소가 덱에 한 번만 들어감 → amortized O(N).
시각화
핵심 아이디어
Deque 를 양방향 확장 가능한 배열 또는 리스트로 구현:
- chunked array (STL
std::deque방식): 고정 크기 블록 여러 개를 관리. 각 블록은 배열. 블록 간 포인터로 연결. - doubly-linked list: 노드 양 끝에 prev, next. head/tail 포인터로 O(1) 접근.
Monotonic Deque 활용 (슬라이딩 윈도우 최대값 예):
- 윈도우를 오른쪽으로 밀면서, 덱에 인덱스 (또는 값) 를 내림차순으로 유지.
- 윈도우 밖으로 나간 인덱스는 front 에서 pop.
- 새 원소가 덱 back 의 원소보다 크면, back 을 pop (더 이상 최댓값 후보가 아니므로).
- 덱의 front 가 항상 현재 윈도우의 최댓값 인덱스.
invariant: 덱에는 윈도우 내 원소 중 “미래에 최댓값이 될 가능성이 있는 후보”만 남는다. 각 원소는 덱에 최대 1 번 push, 1 번 pop → 전체 O(N).
알고리즘
기본 연산
push_front(x):
head 에 노드/블록 추가. O(1).
push_back(x):
tail 에 노드/블록 추가. O(1).
pop_front():
head 노드/블록 제거. O(1).
pop_back():
tail 노드/블록 제거. O(1).
at(i):
chunked array 방식이면 O(1). linked list 는 O(N).
Monotonic Deque (슬라이딩 윈도우 최대)
sliding_window_max(a, k):
dq = empty deque (인덱스 저장)
ans = []
for i = 0..N-1:
// 윈도우 밖 제거
while !dq.empty() and dq.front() < i - k + 1:
dq.pop_front()
// 새 원소보다 작은 후보 제거 (back 에서)
while !dq.empty() and a[dq.back()] <= a[i]:
dq.pop_back()
dq.push_back(i)
if i >= k - 1:
ans.append(a[dq.front()])
return ans
구현
// C++ std::deque 로 슬라이딩 윈도우 최댓값 O(N)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k; cin >> n >> k;
vector<int> a(n);
for (auto& v : a) cin >> v;
deque<int> dq; // 인덱스 저장
vector<int> ans;
for (int i = 0; i < n; i++) {
// 윈도우 밖 제거
while (!dq.empty() && dq.front() < i - k + 1)
dq.pop_front();
// 새 원소보다 작은 후보 제거
while (!dq.empty() && a[dq.back()] <= a[i])
dq.pop_back();
dq.push_back(i);
if (i >= k - 1)
ans.push_back(a[dq.front()]);
}
for (int v : ans) cout << v << " ";
}7 3
1 3 -1 -3 5 3 63 3 5 5 6복잡도
| 항목 | 값 |
|---|---|
| push_front / push_back | O(1) |
| pop_front / pop_back | O(1) |
| at(i) | O(1) (STL deque), O(N) (linked list) |
| 공간 | O(N) |
| monotonic deque (슬라이딩 윈도우) | O(N) 전체 (각 원소 1 번 push + 1 번 pop) |
변형 / 활용
| 응용 | 설명 |
|---|---|
| 슬라이딩 윈도우 최대/최소 | monotonic deque. O(N). |
| BFS 0-1 | 가중치 0 이면 push_front, 1 이면 push_back. 0-1 BFS. |
| 회전 덱 (Josephus) | 원형 큐를 덱으로 구현, 양쪽 회전. |
| palindrome check | 양 끝에서 비교. |
| undo/redo stack | 한쪽 끝에서만 쓰면 스택, 양쪽 쓰면 덱. |
함정
1. 인덱스 vs 값
monotonic deque 에서 인덱스를 저장해야 윈도우 밖 제거가 가능. 값만 저장하면 “어느 게 윈도우 밖인지” 모른다.
2. <= vs < (중복 처리)
슬라이딩 윈도우 최댓값에서 같은 값 여러 개가 있을 때, 뒤쪽 인덱스를 덱에 남겨야 윈도우가 밀려도 안전. 그래서 a[dq.back()] <= a[i] 로 pop.
최솟값은 a[dq.back()] >= a[i] 로 반대.
3. STL deque 는 랜덤 접근 O(1) 이지만, vector 보다 느림
cache locality 가 나쁘다. 잦은 랜덤 접근 + 크기 변동 없으면 vector 가 낫다. deque 는 양쪽 끝 연산이 많을 때만.
4. Java Deque 는 인터페이스
구현체는 ArrayDeque (순환 배열) 또는 LinkedList (양방향 리스트). 보통 ArrayDeque 가 빠름.
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 10866 | 덱 | - | kokoa-lab |
| BOJ 5430 | AC (회전 덱) | - | kokoa-lab |
| BOJ 11003 | 최솟값 찾기 (monotonic deque) | - | kokoa-lab |
| BOJ 2346 | 풍선 터뜨리기 | - | kokoa-lab |
참고
이 글의 용어 (4개)
- 0-1 BFSalgorithm
- 정의 0-1 BFS 는 간선 가중치가 0 또는 1만 존재하는 그래프에서 최단 경로를 O(V + E) 에 구하는 알고리즘. deque 를 사용해 가중치 0 간선은 앞에, 가중치 1…
- 스택 (Stack)algorithm
- 정의 스택 (Stack) 은 LIFO (Last In, First Out) 순서로 원소를 관리하는 추상 자료구조입니다. , , 세 연산만 제공하며, 가장 최근 삽입된 원소만 접근…
- 슬라이딩 윈도우 (Sliding Window)algorithm
- 정의 슬라이딩 윈도우 (Sliding Window) 는 배열/문자열 위에서 고정 크기 또는 가변 크기 윈도우가 한 방향으로 미끄러지며 각 윈도우마다 조건 (합, 최댓값, 중복 여…
- 큐 (Queue)algorithm
- 정의 큐 (Queue) 는 FIFO (First In, First Out) 순서로 원소를 관리하는 추상 자료구조입니다. (또는 ), (또는 ), 세 연산만 제공하며, 가장 먼저 …
💬 댓글