Intuiție: ce înseamnă, de fapt, o „mutație”
Hai să traducem enunțul în limbaj simplu. Avem un cod de referință — virusul pandemic, să-i spunem . Primim o listă de coduri și, pentru fiecare, ne punem o singură întrebare: seamănă „aproape perfect” cu ?
„Aproape perfect” are aici o definiție foarte precisă, formată din două condiții care trebuie îndeplinite în același timp:
- Aceeași lungime — codul are exact câte litere are . Dacă e mai scurt sau mai lung, nici nu mai discutăm.
- Exact o singură literă diferită — pe toate pozițiile coincide cu , mai puțin una.
Imaginează-ți că suprapui cele două șiruri, literă peste literă, ca pe două rânduri într-un tabel. O mutație este un cod care, suprapus peste , are fix o coloană unde literele nu se potrivesc. Nici zero (atunci ar fi identic, nu mutație), nici două sau mai multe.
Pentru abac, șirul Bbac este mutație: prima literă diferă ( devine ), restul coincide. În schimb abac (identic) nu e mutație, iar BBac (două diferențe) nu e mutație.
Ideea cheie
Toată problema se reduce la o funcție mică, pe care o aplicăm pe fiecare cod din listă:
Numără pozițiile pe care codul diferă de . Este mutație dacă și numai dacă lungimile sunt egale și numărul de diferențe este exact .
Odată ce avem această funcție, cele două cerințe devin simple:
- C=1 — câte mutații sunt în listă? Trecem o dată prin listă și numărăm câte coduri trec testul. Atenție: numărăm aparițiile, nu mutațiile distincte — dacă același cod-mutație apare de trei ori, contează de trei ori.
- C=2 — care mutație apare de cele mai multe ori? Ținem un contor de frecvențe (un
mapcu cheie șirul și valoare numărul de apariții). La final alegem mutația cu frecvența maximă; la egalitate, pe cea mai mică lexicografic.
Algoritmul pas cu pas (pe Exemplul 2)
Să urmărim concret cerința cu pandemicul abcD și lista abcdD, XbcD, Xc, XbcD, aXcD, aXcD, aXc, ab. Pandemicul are lungimea .
abcdD— lungime . Eliminat din start, fără să comparăm litere.XbcD— lungime . Comparăm: poziția 1 areXîn loc dea(diferență), pozițiile 2, 3, 4 coincid. O singură diferență → mutație. În map:XbcDajunge la .Xc— lungime . Eliminat.XbcD— din nou mutație. În map:XbcDajunge la .aXcD— lungime , diferă doar pe poziția 2 (Xfață deb). Mutație. În map:aXcDajunge la .aXcD— mutație. În map:aXcDajunge la .aXc— lungime . Eliminat.ab— lungime . Eliminat.
La final avem două mutații cu frecvență maximă : XbcD și aXcD. Departajăm lexicografic: comparăm prima literă, X (cod ASCII ) față de a (cod ASCII ). Cum , șirul XbcD este mai mic, deci el este răspunsul.
Acesta este și momentul cel mai înșelător din problemă: intuiția „de zi cu zi” spune că a vine înaintea lui X (alfabetul mic pare „mai la început”), dar codul ASCII pune toate majusculele (–) înaintea tuturor minusculelor (–). Ordinea reală este deci .
De ce e corect
Corectitudinea funcției de verificare vine direct din definiție: o mutație are, prin enunț, exact o poziție diferită la lungimi egale — iar noi exact asta numărăm. Verificarea lungimii înainte de a compara caractere nu e doar o optimizare, ci o condiție necesară: șiruri de lungimi diferite nu pot fi „suprapuse” și nu pot fi mutații, indiferent câte litere ar avea în comun.
Pentru , un map<string,int> reține fidel câte ori a apărut fiecare mutație, deoarece incrementăm contorul la fiecare apariție validă. Alegerea răspunsului parcurge toate cheile și păstrează perechea cu frecvența maximă; la frecvențe egale reține cheia mai mică. Aici intervine un detaliu elegant: în C++ std::string se compară chiar după codul ASCII, iar std::map<string,int> ține cheile deja sortate crescător. Astfel, comparația standard < este exact ordinea cerută de problemă, fără să scriem nimic special.
Complexitate
Notăm cu numărul de coduri și cu lungimea maximă a unui cod ().
- Verificarea unui cod costă — o singură trecere prin caractere (cu ieșire devreme când diferențele depășesc ).
- C=1 costă : aplicăm verificarea pe fiecare cod.
- C=2 adaugă operațiile pe
map. Fiecare inserare/căutare compară chei de lungime și face pași, deci în total.
Cu și , chiar și cazul rămâne în jur de câteva milioane de operații — mult sub limita de timp.