De ce contează?
Stai la o intersecție și numeri mașinile care trec, pe culori. Nu ții minte ordinea în care au venit — ai doar un carnețel cu un rând pentru fiecare culoare și pui un băț de fiecare dată: roșu, roșu, albastru, roșu... La final nu mai parcurgi nimic: te uiți la carnețel și știi instant câte au fost de fiecare culoare. Exact așa numeri și literele dintr-un șir.
Intuiția
Un șir de caractere e o înșiruire de litere, dar de multe ori nu te interesează ordinea lor, ci de câte ori apare fiecare. Ții un singur tablou mic, cu o căsuță pentru fiecare literă posibilă, și parcurgi șirul o dată: pentru fiecare literă, mai pui un băț în căsuța ei. La final, tabloul îți spune totul despre compoziția șirului — fără să-l mai atingi. Acest tablou se numește vector de frecvențe.
Vezi cum funcționează
Urmărește cum o singură trecere prin șir umple căsuțele tabloului de frecvențe: fiecare literă citită incrementează exact o căsuță, cea care îi corespunde.
Folosește ← și → ca să pășești prin algoritm, sau Redă pentru animație. Observă că tabloul are o căsuță fixă pentru fiecare literă — citirea șirului doar adaugă bețe, nu mută nimic.
Vrei să știi de câte ori apare fiecare literă. Prima idee: iei pe rând a, apoi
b, apoi c... și pentru fiecare reparcurgi tot șirul numărând câte se
potrivesc.
Dacă șirul are n litere și alfabetul are 26 de litere, faci 26 × n operații.
Reparcurgi același șir de 26 de ori, deși ai citit deja fiecare literă — risipă
curată. La șiruri lungi și multe interogări, se simte.
Nu trebuie să reparcurgi nimic. O literă „știe” singură în ce căsuță să se pună:
litera c merge în căsuța cu indexul c - 'a' (a→0, b→1, ...). O singură trecere
prin șir poate aduna toate frecvențele deodată.
Ții un tablou freq[26] inițializat cu 0 și, pentru fiecare literă s[i], faci
freq[s[i] - 'a']++. Gata: o trecere, O(n + alfabet).
Algoritmul pas cu pas
Fie șirul abracadabra (11 litere). Pornim cu tabloul freq plin de zerouri și
parcurgem litera cu literă, incrementând de fiecare dată căsuța s[i] - 'a'.
Prima literă e a, deci a - 'a' = 0. Incrementăm căsuța 0:
1 | 0 | 0 | 0 | 0 |
0 | 1 | 2 | 3 | 4 |
a |
Citim b (b - 'a' = 1), apoi r (r - 'a' = 17), apoi din nou a. După
patru litere citite (a b r a) avem doi de a, un b, un r:
2 | 1 | 0 | 0 | 1 |
0 | 1 | 2 | 3 | 4 |
a |
Continuăm cu c a d a b r a. La final, fiecare literă din abracadabra și-a
pus toate bețele:
5 | 2 | 1 | 1 | 2 |
0 | 1 | 2 | 3 | 4 |
Suma tuturor frecvențelor e mereu egală cu lungimea șirului (aici 5+2+1+1+2 = 11).
E un test rapid de corectitudine: dacă suma nu dă n, ai numărat greșit undeva.
Litera dominantă e cea cu frecvența maximă — aici a, cu 5.
Implementare C++
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char s[] = "abracadabra";
int n = strlen(s);
// o casuta pentru fiecare litera mica: a->0, b->1, ..., z->25
int freq[26] = {0}; // initializare cu 0, esential
// o singura trecere: fiecare litera incrementeaza casuta ei
for (int i = 0; i < n; i++) {
freq[s[i] - 'a']++;
}
// afisam doar literele care chiar apar
for (int c = 0; c < 26; c++) {
if (freq[c] > 0) {
cout << (char)('a' + c) << ": " << freq[c] << "\n";
}
}
// a: 5 b: 2 c: 1 d: 1 r: 2
// litera cea mai frecventa
int maxim = 0;
for (int c = 1; c < 26; c++) {
if (freq[c] > freq[maxim]) {
maxim = c;
}
}
cout << "Cea mai frecventa: " << (char)('a' + maxim) << "\n";
// Cea mai frecventa: a
return 0;
}Pentru a verifica dacă două șiruri sunt anagrame, construiești câte un tablou de frecvențe pentru fiecare și compari cele 26 de poziții:
bool suntAnagrame(const char a[], const char b[]) {
int fa[26] = {0}, fb[26] = {0};
for (int i = 0; a[i]; i++) fa[a[i] - 'a']++;
for (int i = 0; b[i]; i++) fb[b[i] - 'a']++;
for (int c = 0; c < 26; c++) {
if (fa[c] != fb[c]) return false; // o diferenta -> nu sunt anagrame
}
return true;
}
// suntAnagrame("listen", "silent") -> trueComplexitate
Notăm n lungimea șirului și A mărimea alfabetului (26 pentru litere mici, 256
pentru toate caracterele ASCII).
| Caz | Timp | Spațiu |
|---|---|---|
| Numărare (o trecere) | O(n) | O(A) |
| Litera dominantă | O(n + A) | O(A) |
| Test anagrame | O(n + A) | O(A) |
Tabloul are mărime fixă (26 sau 256), independentă de n, deci spațiul e
practic constant. Abordarea naivă, cu reparcurgere pentru fiecare literă, ar fi
fost O(n × A) — același rezultat, mult mai lent.
Trei capcane clasice cu vectorul de frecvențe:
- Nu inițializezi
freqcu 0. Un tablou local necitit conține gunoi;int freq[26] = {0};îl pune pe tot pe zero. Fără asta, numeri pornind de la valori aleatorii. - Majuscule vs minuscule.
A - 'a'dă un index negativ (-32), iar ieșirea din tablou e comportament nedefinit. Dacă șirul are și majuscule, normalizează întâi (de ex. cutolower) sau folosește un tablou de 256 de poziții indexat prin(unsigned char)c. - Indexare greșită. Indexul corect e
s[i] - 'a', nus[i]direct: vrei poziția literei în alfabet (0...25), nu codul ei ASCII.