Declararea și inițializarea vectorilor
Gândeşte-te la un bloc cu N apartamente numerotate de la 1 la N. Fiecare apartament (element) are un număr de apartament (indicele) şi un chiriac (valoarea stocată). Calculatorul ştie exact unde e fiecare apartament fără să caute — acesta e accesul direct O(1).
- 🔑
v[3]= apartamentul 3 → acces instant - 👥 Inserare la mijloc = toți chiriasii de la poziția k în sus se mută un etaj mai sus
- 🗳 Ștergere = toți chiriasii de deasupra se coboară un etaj
Un vector (tablou unidimensional) este o colecție de elemente de același tip, stocate consecutiv în memorie, accesate prin indice.
// Declarare vector de n elemente întregi (maxim 100000) int v[100001]; // indexat de la 0 la 100000 int n; // numărul de elemente folosite efectiv // Citire din tastatură cin >> n; for (int i = 1; i <= n; i++) // indexare de la 1 (stil BAC) cin >> v[i]; // Inițializare cu 0 a întregului tablou int a[100] = {0}; // toți cu 0 int b[] = {3, 1, 4, 1, 5}; // dimensiune dedusă automat: 5
v[1], v[2], ..., v[n]. Tabloul trebuie declarat cu dimensiunea n+1 cel puțin.
v[i] — acces direct la elementul i, indiferent de n
n elemente × sizeof(tip) octeți consecutiv în RAM
Parcurgerea vectorilor — operații frecvente
Sumă, minim, maxim — O(n)
int suma = 0, minim = v[1], maxim = v[1]; int pozMin = 1, pozMax = 1; for (int i = 1; i <= n; i++) { suma += v[i]; if (v[i] < minim) { minim = v[i]; pozMin = i; } if (v[i] > maxim) { maxim = v[i]; pozMax = i; } }
Afișare în ordine inversă
for (int i = n; i >= 1; i--) cout << v[i] << " ";
Număr de elemente pare
int nrPare = 0; for (int i = 1; i <= n; i++) if (v[i] % 2 == 0) nrPare++; cout << "Elemente pare: " << nrPare;
Verificare dacă vectorul este sortat crescător
bool sortat = true; for (int i = 1; i < n; i++) if (v[i] > v[i+1]) { sortat = false; break; }
Căutare secvențială și binară
Căutare secvențială — O(n)
int cautareSecventiala(int v[], int n, int x) { for (int i = 1; i <= n; i++) if (v[i] == x) return i; // returnează poziția return -1; // nu a fost găsit } // Funcționează pe vector NESORTAT sau SORTAT // Best: O(1) Average: O(n/2) Worst: O(n)
Căutare binară — O(log n) — doar pe vector SORTAT
int cautareBinara(int v[], int n, int x) { int st = 1, dr = n; while (st <= dr) { int mij = (st + dr) / 2; if (v[mij] == x) return mij; // găsit! else if (v[mij] < x) st = mij + 1; // x e în dreapta else dr = mij - 1; // x e în stânga } return -1; }
Pas 1: st=1, dr=7, mij=4 → v[4]=7 → GĂSIT la poziția 4
Vectorul este generat sortat. Alege algoritmul și ținta, apoi compară numărul de pași.
Sortarea vectorilor
Bubble Sort — cel mai cerut la BAC
void bubbleSort(int v[], int n) { for (int i = 1; i <= n-1; i++) for (int j = 1; j <= n-i; j++) if (v[j] > v[j+1]) swap(v[j], v[j+1]); // O(n²) — bun pentru demonstrații, mai lent în practică }
Selection Sort — explicat pas cu pas
void selectionSort(int v[], int n) { for (int i = 1; i <= n-1; i++) { int pozMin = i; for (int j = i+1; j <= n; j++) if (v[j] < v[pozMin]) pozMin = j; if (pozMin != i) swap(v[i], v[pozMin]); } // O(n²) în toate cazurile, O(1) spațiu }
Insertion Sort — eficient pe date aproape sortate
void insertionSort(int v[], int n) { for (int i = 2; i <= n; i++) { int cheie = v[i], j = i - 1; while (j >= 1 && v[j] > cheie) { v[j+1] = v[j]; j--; } v[j+1] = cheie; } }
sort() din STL — O(n log n)
#include <algorithm> sort(v + 1, v + n + 1); // crescător (v indexat de la 1) sort(v + 1, v + n + 1, greater<int>()); // descrescător
Compară Bubble, Selection și Insertion Sort vizual. Urmărește numărul de comparații și interschimbări.
Interclasarea (Merge) a doi vectori sortați
void interclasare(int a[], int m, int b[], int n, int c[]) { int i = 1, j = 1, k = 0; while (i <= m && j <= n) { if (a[i] <= b[j]) c[++k] = a[i++]; else c[++k] = b[j++]; } // Copiem restul din A sau B while (i <= m) c[++k] = a[i++]; while (j <= n) c[++k] = b[j++]; // Complexitate: O(m+n) timp și spațiu }
A = [1, 3, 5, 7], B = [2, 4, 6]
C = [1, 2, 3, 4, 5, 6, 7] ✅
Introdu valorile (vor fi sortate automat) și urmărește cum cei doi pointeri i și j construiesc rezultatul C.
Inserare și ștergere elemente
Inserare pe poziția p — O(n)
void insereaza(int v[], int &n, int p, int val) { // Deplasăm elementele de la p la n cu o poziție la dreapta for (int i = n; i >= p; i--) v[i+1] = v[i]; v[p] = val; n++; }
Ștergere de pe poziția p — O(n)
void sterge(int v[], int &n, int p) { // Deplasăm elementele de la p+1 la n cu o poziție la stânga for (int i = p; i < n; i++) v[i] = v[i+1]; n--; }
📌 Exemplu pas cu pas — Inserare
[3, 7, 1, 9, 4, 2], n = 6. Inserăm 99 pe poziția 3. - v[6] = v[5] = 2 →
[3, 7, 1, 9, 4, 2, 2] - v[5] = v[4] = 4 →
[3, 7, 1, 9, 4, 4, 2] - v[4] = v[3] = 9 →
[3, 7, 1, 9, 9, 4, 2] - v[3] = v[2] = 1 →
[3, 7, 1, 1, 9, 4, 2] - v[3] = 99; n = 7 →
[3, 7, 99, 1, 9, 4, 2]
⚠️ Bucla merge de la i = n spre p (descrescător), altfel suprascriem valori încă neutilizate.
📌 Exemplu pas cu pas — Ștergere
[3, 7, 99, 1, 9, 4, 2], n = 7. Ștergem poziția 3 (valoarea 99). - v[3] = v[4] = 1 →
[3, 7, 1, 1, 9, 4, 2] - v[4] = v[5] = 9 →
[3, 7, 1, 9, 9, 4, 2] - v[5] = v[6] = 4 →
[3, 7, 1, 9, 4, 4, 2] - v[6] = v[7] = 2 →
[3, 7, 1, 9, 4, 2, 2] - n = 6 (ignorăm „2" extra) →
[3, 7, 1, 9, 4, 2]
⚠️ Bucla merge de la p spre n − 1 (crescător). Decrementăm n la final.
Vectorul are 6 elemente și capacitate 8. Alege o poziție, apoi apasă Inserează sau Șterge.
n − p + 1 (inserare) sau n − p (ștergere). Cel mai rău caz: poziția 1 ⇒ n deplasări. Cel mai bun caz: poziția n+1 (final) ⇒ 0 deplasări.
Vectori de frecvență (Counting Sort)
Când valorile sunt din domeniu mic [0, K], putem număra aparițiile în O(n+K).
int freq[1001] = {0}; // domeniu valori 0..1000 // Numărăm aparițiile for (int i = 1; i <= n; i++) freq[v[i]]++; // Afișăm valorile sortate (Counting Sort) for (int val = 0; val <= 1000; val++) for (int j = 0; j < freq[val]; j++) cout << val << " "; // Cel mai frecvent element int maxFreq = 0, elMaxFreq = -1; for (int i = 0; i <= 1000; i++) if (freq[i] > maxFreq) { maxFreq = freq[i]; elMaxFreq = i; }
📌 Exemplu — frecvența cifrelor
v = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], n = 11.După parcurgere,
freq[] arată așa:
| cifră (i) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| freq[i] | 0 | 2 | 1 | 2 | 1 | 3 | 1 | 0 | 0 | 1 |
1 1 2 3 3 4 5 5 5 6 9 ✅Cel mai frecvent: 5 (apare de 3 ori).
📌 Exemplu — eliminarea duplicatelor în O(n)
// Afișează DOAR valorile distincte, în ordinea apariției bool apar[1001] = {false}; for (int i = 1; i <= n; i++) { if (!apar[v[i]]) { cout << v[i] << " "; apar[v[i]] = true; } }
📌 Exemplu — intersecția a două mulțimi
// Elementele comune între v[] (n elemente) și w[] (m elemente) bool inV[1001] = {false}; for (int i = 1; i <= n; i++) inV[v[i]] = true; for (int j = 1; j <= m; j++) if (inV[w[j]]) cout << w[j] << " "; // O(n + m + K) în loc de O(n·m) naiv
Introdu cifre 0..9 (separate prin spațiu sau virgulă). Faza 1 numără aparițiile, faza 2 reconstruiește vectorul sortat.
freq[]:map<int,int> (clasa a XI-a) sau de redenumire (mapare la 0..K).
Demo Interactiv — Inserare, Ștergere, Căutare
Vectorul de mai jos are 8 sloturi (capacitate maximă). Sloturile goale sunt gri. Apasă butoanele pentru a vedea operațiile pas cu pas.
- Inserare la poziția k → mută
n−kelemente la dreapta → O(n) - Ștergere de la poziția k → mută
n−kelemente la stânga → O(n) - Căutare secvențială → parcurge tot vectorul în cel mai rău caz → O(n)
- Acces direct
v[i]→ instant, nu depinde de n → O(1)
Probleme rezolvate — vectori
5 probleme clasice cu enunț, exemplu și soluție comentată. Încearcă întâi singur, apoi deschide soluția.
5 3 7 2 7 1
7 2
💡 Vezi soluția
#include <iostream> using namespace std; int v[105]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> v[i]; int mx = v[1], poz = 1; for (int i = 2; i <= n; i++) if (v[i] > mx) { mx = v[i]; poz = i; } // > strict ⇒ prima apariție cout << mx << " " << poz; return 0; }
Idee: inițializăm cu primul element, parcurgem o singură dată. > strict (nu ≥) ⇒ păstrăm prima apariție. Complexitate O(n).
6 4 7 9 11 15 13
7 11 13
💡 Vezi soluția
#include <iostream> using namespace std; bool prim(int x) { if (x < 2) return false; for (int d = 2; d * d <= x; d++) if (x % d == 0) return false; return true; } int main() { int n, x; cin >> n; for (int i = 1; i <= n; i++) { cin >> x; if (prim(x)) cout << x << " "; } return 0; }
Idee: verificare prim până la √x (condiția d*d <= x evită sqrt()). Pentru valori mari sau apeluri repetate, pre-calculează un ciur.
7 1 1 2 3 3 3 5
4 1 2 3 5
💡 Vezi soluția
#include <iostream> using namespace std; int v[100005]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> v[i]; int k = 1; // poziția unde scriem următorul element unic for (int i = 2; i <= n; i++) if (v[i] != v[k]) v[++k] = v[i]; cout << k << "\n"; for (int i = 1; i <= k; i++) cout << v[i] << " "; return 0; }
Tehnică two-pointers: k arată ultima poziție validă, i scanează. Pentru că vectorul e sortat, duplicatele sunt vecine. Complexitate O(n), memorie O(1) suplimentară.
8 1 3 5 2 4 6 7 1
4
💡 Vezi soluția
#include <iostream> using namespace std; int main() { int n, x, prev = 0; cin >> n; int lung = 0, maxL = 0; for (int i = 1; i <= n; i++) { cin >> x; if (i == 1 || x > prev) lung++; else lung = 1; // rupt — pornim secvența nouă cu x if (lung > maxL) maxL = lung; prev = x; } cout << maxL; return 0; }
Idee: ținem doar prev (elementul anterior) și lung (lungimea curentă). Nu e nevoie să stocăm tot vectorul. Complexitate O(n), memorie O(1).
Pe exemplu: 1<3<5 (3), apoi rupere; 2<4<6<7 (4), apoi rupere; 1 (1). Maxim = 4.
9 -2 1 -3 4 -1 2 1 -5 4
6
💡 Vezi soluția
#include <iostream> using namespace std; int main() { int n, x; cin >> n; int sumaCur = 0, sumaMax; for (int i = 1; i <= n; i++) { cin >> x; sumaCur = (i == 1) ? x : max(x, sumaCur + x); if (i == 1 || sumaCur > sumaMax) sumaMax = sumaCur; } cout << sumaMax; return 0; }
Algoritm Kadane: pentru fiecare poziție, decidem dacă extindem subsecvența curentă (sumaCur + x) sau repornim de la x. Reținem maximul. Complexitate O(n).
Pe exemplu: subsecvența [4, -1, 2, 1] are suma 6.
Probleme tip BAC — vectori
bool aparut[1001] = {false}; for (int i = 1; i <= n; i++) { if (!aparut[v[i]]) { cout << v[i] << " "; aparut[v[i]] = true; } } // O(n) timp, O(K) spațiu
int m = 1; // noul n după eliminare for (int i = 2; i <= n; i++) if (v[i] != v[i-1]) v[++m] = v[i]; n = m; // actualizăm dimensiunea
int freq[100001] = {0}; bool ok = true; for (int i = 1; i <= n; i++) { if (v[i] < 1 || v[i] > n || freq[v[i]] > 0) { ok = false; break; } freq[v[i]]++; } cout << (ok ? "DA" : "NU");
Ciurul lui Eratostene
Ciurul lui Eratostene este algoritmul clasic pentru a găsi toate numerele prime până la un număr dat N, în timp O(N log log N). Este unul dintre cei mai eficienți algoritmi pentru această problemă și apare frecvent la BAC.
Ideea: pornești de la 2, marchezi toți multiplii lui 2 ca neprimi, treci la 3, marchezi multiplii lui 3, și tot așa.
Implementare standard
#include <iostream> using namespace std; const int NMAX = 1000001; bool ciur[NMAX]; // ciur[i] = true dacă i este NEPRIM void genereazaCiur(int n) { ciur[0] = ciur[1] = true; // 0 și 1 nu sunt prime for (int i = 2; (long long)i * i <= n; i++) { if (!ciur[i]) { // i este prim for (int j = i * i; j <= n; j += i) // pornim de la i² ciur[j] = true; // multiplii lui i sunt neprimi } } } int main() { int n; cin >> n; genereazaCiur(n); for (int i = 2; i <= n; i++) if (!ciur[i]) cout << i << " "; return 0; }
Toți multiplii mai mici decât i² (adică 2·i, 3·i, …, (i−1)·i) au fost deja marcați de pași anteriori. Astfel, evităm marcări duplicate și algoritmul este mai rapid.
Numărarea numerelor prime — problemă BAC frecventă
// Câte numere prime există în intervalul [a, b]? genereazaCiur(b); int cnt = 0; for (int i = max(2, a); i <= b; i++) if (!ciur[i]) cnt++; cout << cnt;
Verificare rapidă: este x prim?
// După apelul genereazaCiur(NMAX-1): bool estePrim(int x) { return x >= 2 && !ciur[x]; }
Practic liniar pentru N ≤ 10⁶
Un vector bool de N elemente ≈ 1 MB pentru N=10⁶
ciur[] ca variabilă globală, nu în main(). Un vector de 10⁶ elemente declarat local provoacă stack overflow!
Start: toți false (neprimii vor fi marcați true)
Marchează multiplii lui 2: 4, 6, 8, 10, 12, 14, 16, 18, 20
Marchează multiplii lui 3: 9, 15 (6, 12, 18 deja marcate)
4² = 16 > 20 → stop
Prime găsite: 2, 3, 5, 7, 11, 13, 17, 19 ✅
📊 Ciur vs verificare divizori — de ce contează?
Pentru a verifica dacă un singur număr e prim, metoda cu divizori O(√x) e perfectă. Dar dacă trebuie să răspunzi la q întrebări de tipul „este x prim?", precalculul cu Ciur câștigă net:
| Scenariu | Divizori O(q·√N) | Ciur O(N log log N + q) | Câștigător |
|---|---|---|---|
| 1 număr, N = 10⁶ | ~1000 operații | ~10⁶ + 1 operații | Divizori ✅ |
| 10⁴ numere, N = 10⁶ | ~10⁷ operații | ~10⁶ + 10⁴ ≈ 10⁶ | Ciur ✅ (10×) |
| 10⁵ numere, N = 10⁶ | ~10⁸ operații (~ 1 s) | ~10⁶ (~ 10 ms) | Ciur ✅ (100×) |
🔧 Variante avansate ale Ciurului
a) Cel mai mic divizor prim (SPF)
În loc de bool, stocăm cel mai mic factor prim al fiecărui număr. Foarte util pentru a factoriza rapid orice x ≤ N:
int spf[NMAX]; // spf[i] = cel mai mic divizor prim al lui i void construSPF(int n) { for (int i = 2; i <= n; i++) if (spf[i] == 0) for (int j = i; j <= n; j += i) if (spf[j] == 0) spf[j] = i; } // Factorizare în O(log x): while (x > 1) { cout << spf[x] << " "; x /= spf[x]; }
b) Numărarea divizorilor cu Ciur
int nrDiv[NMAX] = {0}; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j += i) nrDiv[j]++; // nrDiv[12] = 6 (divizorii: 1,2,3,4,6,12)
Complexitate: O(N · H(N)) ≈ O(N log N), unde H(N) e seria armonică.
🎯 Probleme tipice BAC cu Ciur
- Câte prime sunt în [a, b]? — precalculează ciur(b), apoi contează în O(b − a).
- Sumă/produs de prime ≤ N — un singur for după ciur.
- Cel mai mare prim ≤ N — iterează descendent.
- Numere prime gemene (p și p+2 ambele prime) — verifică perechi consecutive.
- Conjectura lui Goldbach — pentru orice n par ≥ 4, găsește p prim astfel încât n − p e tot prim.
- Test rapid de primalitate pentru valori multiple — query în O(1) după precalcul.
Vezi cum sunt marcați multiplii fiecărui prim. Numerele tăiate sunt neprimi (compuse).