Deque (coadă cu două capete)
Un deque (Double-Ended Queue, pronunțat “dec”) este o structură de date care permite adăugarea și eliminarea elementelor de la ambele capete - atât din față cât și din spate.
Este o combinație între stivă și coadă.
Operații fundamentale
| Operație | Ce face | Complexitate |
|---|---|---|
push_back(x) |
Adaugă la spate | O(1) |
push_front(x) |
Adaugă în față | O(1) |
pop_back() |
Scoate din spate | O(1) |
pop_front() |
Scoate din față | O(1) |
front() |
Elementul din față | O(1) |
back() |
Elementul din spate | O(1) |
empty() |
Verifică dacă e gol | O(1) |
Exemplu vizual
push_back(3): [3]
push_back(7): [3, 7]
push_front(1): [1, 3, 7]
push_back(9): [1, 3, 7, 9]
pop_front(): [3, 7, 9]
pop_back(): [3, 7]
front = 3, back = 7
Implementare pe vector
Folosim un vector mare cu un offset la mijloc, ca să putem crește în ambele direcții:
int dq[200001];
int st, dr; // st = primul element, dr = ultimul
void init() {
st = 100001;
dr = 100000;
}
void push_back(int x) {
dr++;
dq[dr] = x;
}
void push_front(int x) {
st--;
dq[st] = x;
}
void pop_back() {
if (st <= dr) dr--;
}
void pop_front() {
if (st <= dr) st++;
}
int front() {
return dq[st];
}
int back() {
return dq[dr];
}
bool empty() {
return st > dr;
}
int size() {
return dr - st + 1;
}Pornim cu st și dr la mijlocul
vectorului - astfel avem loc să creștem în ambele direcții.
Problema principală: minimul pe fereastră glisantă în O(n)
Aceasta este problema pentru care deque-ul este cel mai cunoscut. Am văzut-o la Sliding Window - acolo o rezolvam în O(n*K). Cu deque, o rezolvăm în O(n).
Problema
Avem un vector cu n elemente și un număr
K. Pentru fiecare fereastră de K
elemente consecutive, găsim minimul.
Vector: 3 1 4 1 5 9 2 6 K = 3
Ferestre:
[3 1 4] → min = 1
[1 4 1] → min = 1
[4 1 5] → min = 1
[1 5 9] → min = 1
[5 9 2] → min = 2
[9 2 6] → min = 2
Ideea
Menținem un deque cu indicii elementelor candidate la minim. Deque-ul este mereu crescător după valori (cel mai mic e în față).
La fiecare pas: 1. Scoatem din față indicii
care au ieșit din fereastră 2. Scoatem din
spate indicii cu valori mai mari sau egale decât
elementul curent (nu vor fi niciodată minim) 3.
Adăugăm indicele curent la spate 4.
Minimul ferestrei curente e
v[front()]
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int v[100001];
int dq[100001]; // deque cu indici
int st, dr;
int n, k;
int main()
{
fin >> n >> k;
for (int i = 1; i <= n; i++)
fin >> v[i];
st = 1; dr = 0;
for (int i = 1; i <= n; i++) {
// Scoatem indicii care au ieșit din fereastră
while (st <= dr && dq[st] < i - k + 1)
st++;
// Scoatem din spate elementele mai mari (nu vor fi minim)
while (st <= dr && v[dq[dr]] >= v[i])
dr--;
// Adăugăm indicele curent
dr++;
dq[dr] = i;
// Afișăm minimul ferestrei (după ce avem primele K elemente)
if (i >= k)
fout << v[dq[st]] << " ";
}
return 0;
}date.in:
8 3
3 1 4 1 5 9 2 6date.out:
1 1 1 1 2 2Pas cu pas
| i | v[i] | Deque (indici) | Deque (valori) | Fereastră | Minim |
|---|---|---|---|---|---|
| 1 | 3 | [1] | [3] | - | - |
| 2 | 1 | [2] | [1] | - | - |
| 3 | 4 | [2, 3] | [1, 4] | [3,1,4] | 1 |
| 4 | 1 | [4] | [1] | [1,4,1] | 1 |
| 5 | 5 | [4, 5] | [1, 5] | [4,1,5] | 1 |
| 6 | 9 | [4, 5, 6] | [1, 5, 9] | [1,5,9] | 1 |
| 7 | 2 | [7] | [2] | [5,9,2] | 2 |
| 8 | 6 | [7, 8] | [2, 6] | [9,2,6] | 2 |
Pasul 4 explicat: - v[4] = 1 - Scoatem din
spate: v[3]=4 >= 1 → scoatem.
v[2]=1 >= 1 → scoatem. Deque gol. - Adăugăm
indicele 4. - Indicele 1 a ieșit din fereastră
(1 < 4-3+1 = 2), dar deja nu mai e. - Minim:
v[4] = 1
Pasul 7 explicat: - v[7] = 2 - Scoatem din față:
indicele 4 a ieșit (4 < 7-3+1 = 5) → scoatem. -
Scoatem din spate: v[6]=9 >= 2,
v[5]=5 >= 2 → scoatem tot. - Deque: [7], minim =
v[7] = 2
De ce O(n)?
Fiecare indice intră în deque o singură dată (push_back) și iese cel mult o dată (pop_front sau pop_back). Total: 2n operații = O(n).
Varianta cu maxim pe fereastră
Singura diferență: schimbăm >= în
<= la eliminarea din spate:
// Pentru MAXIM:
while (st <= dr && v[dq[dr]] <= v[i])
dr--;Deque-ul devine descrescător - cel mai mare e în față.
Problema: diferența maximă pe fereastră
Vrem max(v[i..i+K-1]) - min(v[i..i+K-1]) maximă
peste toate ferestrele.
Folosim două deque-uri: unul pentru minim, altul pentru maxim.
int dqMin[100001], stMin = 1, drMin = 0;
int dqMax[100001], stMax = 1, drMax = 0;
for (int i = 1; i <= n; i++) {
while (stMin <= drMin && dqMin[stMin] < i - k + 1) stMin++;
while (stMin <= drMin && v[dqMin[drMin]] >= v[i]) drMin--;
drMin++; dqMin[drMin] = i;
while (stMax <= drMax && dqMax[stMax] < i - k + 1) stMax++;
while (stMax <= drMax && v[dqMax[drMax]] <= v[i]) drMax--;
drMax++; dqMax[drMax] = i;
if (i >= k) {
int dif = v[dqMax[stMax]] - v[dqMin[stMin]];
if (dif > maxDif) maxDif = dif;
}
}Deque vs Stivă vs Coadă
| Stivă | Coadă | Deque | |
|---|---|---|---|
| Adaugă | vârf | spate | ambele capete |
| Scoate | vârf | față | ambele capete |
| Principiu | LIFO | FIFO | ambele |
| Generalizare | - | - | include stivă + coadă |
Deque-ul poate simula atât stiva (push_back + pop_back) cât și coada (push_back + pop_front).
Greșeli frecvente
1. Stocarea valorilor în loc de indici
dq[dr] = v[i]; // GREȘIT - nu putem verifica dacă a ieșit din fereastră
dq[dr] = i; // CORECT - stocăm indicele, accesăm v[dq[...]] pentru valoareAvem nevoie de indici ca să verificăm
dq[st] < i - k + 1 (ieșit din fereastră).
2. Ordinea operațiilor
Ordinea corectă la fiecare pas: 1. Scoatem din față (ieșiți din fereastră) 2. Scoatem din spate (mai mari/mici decât curentul) 3. Adăugăm curentul 4. Citim minimul/maximul
Dacă inversăm, putem avea indici invalizi.
3. Afișarea înainte de a avea K elemente
if (i >= k) // afișăm doar după ce avem prima fereastră completăCe să reții
- Deque permite operații O(1) la ambele capete.
- Problema clasică: min/max pe fereastră glisantă în O(n).
- Stocăm indici în deque, nu valori.
- Deque-ul este mereu monoton (crescător pentru min, descrescător pentru max).
- Fiecare element intră și iese cel mult o dată → O(n) total.
- Poate înlocui stiva (push_back + pop_back) sau coada (push_back + pop_front).