Stringek és karaktergyakoriság
LeetCode DSA témakör: frequency counting, two-pass scans, anagram patterns, normalization
A stringes interjúfeladatok felszínen nagyon különbözőnek tűnnek, de sok közülük ugyanarra a néhány ötletre redukálható: normalizálás, számlálás, a megfelelő karakterállapot megőrzése, és annak eldöntése, hogy tömb vagy
HashMapa jobb gyakorisági struktúra.
1. Definíció
Miről szól ez a téma?
Ez a téma arról szól, hogyan oldasz meg stringes feladatokat úgy, hogy explicit módon modellezed a karaktereket.
Ahelyett, hogy csak teljes szavakban gondolkodnál, sokszor inkább ezt nézed:
- karaktergyakoriság,
- első vagy utolsó előfordulási pozíció,
- normalizált reprezentáció,
- és hogy számít-e a sorrend vagy csak a multihalmaz tartalom.
Miért létezik?
Mert sok beginner és intermediate interjúkérdés valójában elrejtett számlálási feladat.
A prompt beszélhet:
- anagramokról,
- duplikátumokról,
- érvényes karakterekről,
- palindromokról,
- első egyedi karakterről,
- vagy csoportosított szavakról,
miközben a valódi bottleneck sokszor a frequency tracking.
Hová illeszkedik?
Ez a téma közvetlenül a tömbalapok után jön.
Ez természetes, mert a stringes feladatok gyakran végül ilyenekké válnak:
- karakter-számláló tömbös feladat,
HashMaplookup feladat,- vagy kétpasszos scan feladat.
Mi NEM ez a téma?
Ez nem az advanced string matchingről szól, mint a KMP vagy a Rabin-Karp.
Ez a korábbi interjús rétegről szól:
- számlálás,
- normalizálás,
- csoportosítás,
- és scanelés.
Miért szeretik ezt az interjúztatók?
A string feladatok jók arra, hogy lásd, tudsz-e:
- pontosan contractot olvasni,
- észrevenni a rejtett feltételezéseket,
- kompakt state modellt választani,
- és elkerülni a túltervezést.
Sok rossz stringes megoldás ott hibázik, hogy a jelölt nem tisztázta, mit kell ignorálni, normalizálni vagy számolni.
2. Alapfogalmak
String mint sorozatprobléma
Sok stringes feladat valójában karakterekkel dolgozó sorozatprobléma.
Ezért a hasznos kérdések gyakran ezek:
- mi a munkaegység,
- milyen state-nek kell fennmaradnia,
- és számít-e a sorrend?
Gyakoriságszámlálás
A gyakoriságszámlálás azt nézi, egy karakter hányszor jelenik meg.
Ez hasznos például:
- anagram ellenőrzésnél,
- duplikátum detektálásnál,
- majority-szerű gondolkodásnál,
- és kanonikus reprezentáció építésénél.
Tömb versus `HashMap`
Ha az ábécé kicsi és fix, akkor egy tömb sokszor a legtisztább választás.
Példák:
- kis angol betűk,
- nagybetűk,
- számjegyek,
- vagy ASCII-közeli promptok.
Ha a karaktertartomány nagyobb vagy bizonytalan, a HashMap<Character, Integer> biztonságosabb lehet.
Normalizálás
A normalizálás azt jelenti, hogy különbözőnek látszó inputokat közös, összehasonlítható formára alakítasz.
Tipikus lépések:
- lowercasing,
- szóközök kihagyása,
- írásjelek kihagyása,
- trimming,
- vagy sorted / counted signature készítése.
Kétpasszos scan
A two-pass scan gyakori stringes feladatokban.
Első passz:
- számlálást építesz,
- pozíciót tárolsz,
- vagy szerkezetet gyűjtesz.
Második passz:
- megkeresed az első jó választ,
- összehasonlítod a countokat,
- vagy grouped outputot építesz.
Kanonikus reprezentáció
A kanonikus reprezentáció egy stabil forma, amely lehetővé teszi, hogy a logikailag azonos stringek ugyanúgy hasonlítsanak.
Példák:
- rendezett karakterek anagramokhoz,
- count-signature mint
a2#b1#c0..., - normalizált lowercase alfanumerikus forma.
Sorrendérzékeny versus sorrendfüggetlen feladat
Ezt korán el kell dönteni.
Ha a sorrend számít, akkor a frequency-only megoldás kevés lehet.
Ha a sorrend nem számít, az eredeti elrendezés megőrzése felesleges munka lehet.
Karaktertartomány-feltételezések
Mindig kérdezd meg, milyen karakterek jelenhetnek meg.
Ez dönti el, hogy:
- elég-e az
int[26], - elég-e az
int[128], - vagy map-alapú megoldás kell.
Stringek Java-ban
Java-ban a String immutable.
Ez azt jelenti, hogy a cikluson belüli ismételt összefűzés drága lehet.
Tiszta számlálási feladatoknál általában kerülöd a fölösleges intermediate stringek építését.
Felismerési jelek
Ez a téma kapcsoljon be, ha a prompt olyan szavakat használ, mint:
- anagram,
- ismétlődő karakter,
- uniqueness,
- normalizált egyenlőség,
- vagy azonos betűtartalom szerinti csoportosítás.
3. Gyakorlati használat
Mikor jó első lépés a számlálás?
Akkor, ha a prompt azt kérdezi, hogy két string ugyanazokat a karaktereket tartalmazza-e, hogy egy karakter pontosan egyszer szerepel-e, vagy hogy több szó azonos betűösszetételű-e.
Mikor kell először normalizálni?
Akkor, ha a nagybetű-kisbetű különbség, az írásjelek vagy a whitespace nem számíthatnak a válaszban.
Mikor jobb a tömb, mint a map?
A tömböt válaszd, ha:
- fix az ábécé,
- a prompt garantálja a lowercase English letters inputot,
- vagy szorosabb memória- és konstans faktor kell.
Mikor jobb a map, mint a tömb?
A mapet válaszd, ha:
- nem fix a karaktertartomány,
- Unicode vagy vegyes szimbólumok is jöhetnek,
- vagy a prompt nem indokol kis domain feltételezést.
Tipikus interjús flow
Egy tiszta magyarázat gyakran így hangzik:
- „Itt csak a karaktergyakoriság számít, az eredeti sorrend nem.”
- „A prompt garantálja a lowercase English letters bemenetet.”
- „Ezért elég egy
int[26].” - „Egy passzban frissítem a countokat.”
- „Utána vagy összehasonlítom, vagy második passzban megkeresem a választ.”
Gyakori optimalizációs út
Gyakori út:
- naiv nested comparison,
- majd sorting,
- majd direkt counting.
A sorting sokszor elfogadható, de kis domain mellett a counting gyakran egyszerűbb és gyorsabb.
Pair-programming nyelv
Hasznos mondatok:
- „Ez valójában frequency probléma.”
- „Itt a sorrend irreleváns, ezért canonical form kell.”
- „Fix az alphabet, ezért a tömb jobb, mint a map.”
- „Compare előtt normalizálok.”
- „Ez két passzt igényel: először count, utána answer.”
4. Kódpéldák
Alap példa — valid anagram `int[26]`-tal
Ez a verzió lowercase English letters bemenetet feltételez.
public class ValidAnagramArray {
public static boolean isAnagram(String left, String right) {
if (left == null || right == null || left.length() != right.length()) {
return false;
}
int[] counts = new int[26];
for (int index = 0; index < left.length(); index++) {
counts[left.charAt(index) - 'a']++;
counts[right.charAt(index) - 'a']--;
}
for (int count : counts) {
if (count != 0) {
return false;
}
}
return true;
}
}
Miért működik:
- azonos stringeknél a különbségcountok nullára futnak ki,
- kicsi a state,
- és lineáris az idő.
Alap példa — első egyedi karakter két passzal
public class FirstUniqueCharacter {
public static int firstUniqueIndex(String text) {
if (text == null || text.isEmpty()) {
return -1;
}
int[] counts = new int[26];
for (int index = 0; index < text.length(); index++) {
counts[text.charAt(index) - 'a']++;
}
for (int index = 0; index < text.length(); index++) {
if (counts[text.charAt(index) - 'a'] == 1) {
return index;
}
}
return -1;
}
}
Ez klasszikus two-pass scan:
- először számolsz,
- utána megkeresed az első jó pozíciót.
Haladó példa — anagram csoportosítás canonical signature-rel
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GroupAnagrams {
public static List<List<String>> group(String[] words) {
Map<String, List<String>> groups = new HashMap<>();
for (String word : words) {
String signature = buildSignature(word);
groups.computeIfAbsent(signature, ignored -> new ArrayList<>()).add(word);
}
return new ArrayList<>(groups.values());
}
private static String buildSignature(String word) {
int[] counts = new int[26];
for (int index = 0; index < word.length(); index++) {
counts[word.charAt(index) - 'a']++;
}
StringBuilder builder = new StringBuilder();
for (int count : counts) {
builder.append('#').append(count);
}
return builder.toString();
}
}
A kulcslépés egy olyan kanonikus reprezentáció építése, amely ignorálja a sorrendet, de megőrzi az összetételt.
Haladó példa — palindrome ellenőrzés normalizálással
public class NormalizedPalindrome {
public static boolean isPalindrome(String text) {
if (text == null) {
return false;
}
int left = 0;
int right = text.length() - 1;
while (left < right) {
char leftChar = Character.toLowerCase(text.charAt(left));
char rightChar = Character.toLowerCase(text.charAt(right));
if (!Character.isLetterOrDigit(leftChar)) {
left++;
continue;
}
if (!Character.isLetterOrDigit(rightChar)) {
right--;
continue;
}
if (leftChar != rightChar) {
return false;
}
left++;
right--;
}
return true;
}
}
Ez jó emlékeztető arra, hogy nem minden stringes feladat frequency task először.
Néha a normalizálás és a pointermozgás a valódi modell.
Gyakori hiba — rossz domain feltételezés
public class BrokenFrequencyCheck {
public static boolean containsDuplicateLetter(String text) {
int[] counts = new int[26];
for (int index = 0; index < text.length(); index++) {
counts[text.charAt(index) - 'a']++;
}
for (int count : counts) {
if (count > 1) {
return true;
}
}
return false;
}
}
Ez elromlik, ha az input tartalmazhat nagybetűt, számjegyet, írásjelet vagy nem angol karaktert.
A valódi bug nem szintaxis.
Hanem egy ki nem mondott domain feltételezés.
5. Trade-offok
| Szempont | Részletek |
|---|---|
| ⚡ Performance | A frequency arrayek és a two-pass scannek általában lineárisak, és nagyon jók a konstans faktoraik, ha fix az ábécé. |
| 💾 Memory | A tömb kompakt, a map rugalmasabb, de nehezebb. |
| 🔧 Maintainability | A counting megoldások rövidek és review-barátok, de csak akkor, ha a domain-feltételezések explicitek. |
| 🔄 Flexibility | A map-alapú megoldások jobban kezelik a nagyobb karakterkészletet; a tömb szűkebb, de feszesebb megoldás. |
Performance trade-off: A sorting sokszor jó elég, de kis domainnél a counting általában jobb.
Memory trade-off: Egy int[26] nagyon olcsó; a map rugalmasságot vesz nagyobb overheaddel.
Maintainability trade-off: A legtisztább kód sokszor az, amelyiknek a feltételezései ki vannak mondva.
Flexibility trade-off: Minél változatosabb az input, annál kevésbé biztonságos a hard-coded tömbdomain.
6. Gyakori hibák
Lowercase letters feltételezése bizonyíték nélkül
Ez az egyik leggyakoribb csendes bug.Normalizálási szabályok kihagyása
A case, a whitespace és az írásjelek sokszor megváltoztatják a helyes modellt.Sorting használata, amikor a counting egyszerűbb
A sorting valid, de lehet fölösleges munka.Counting használata, amikor a sorrend még mindig számít
A frequency önmagában nem őriz sorrendinformációt.Sok ideiglenes string építése feleslegesen
Java-ban a loopon belüli string-összefűzés drágább lehet, mint kellene.A two-pass szerkezet eltévesztése
Néhány feladatnál előbb count kell, utána külön döntési passz.A karakterdomain ki nem mondása
A26,52,128és aHashMapkülönböző tervezési döntések, nem felcserélhető varázsszámok.
7. Mélyebb összefüggések
Miért redukálódnak jól a stringes feladatok?
A string promptok gyakran azért érződnek maszatosnak, mert a természetes nyelv maszatos.
De sokuk jól redukálható, ha megkérdezed, hogy a feladat valójában:
- normalizálás utáni egyenlőségről,
- multihalmaz-egyenlőségről,
- ritka karakter pozíciójáról,
- vagy csoportosítható tartalomról szól-e.
Miért ilyen népszerűek a counting arrayek?
Azért, mert a szükséges state-et egy kicsi, fix struktúrába sűrítik.
Ez ad:
- kiszámítható memóriát,
- kiszámítható hozzáférést,
- és egyszerű review-logikát.
A kanonikus forma ereje
A kanonikus formák lehetővé teszik, hogy másképp kinéző, de a prompt szabályai szerint azonos inputok ugyanúgy hasonlítsanak.
Ezért tér vissza újra és újra a sorted string, a count signature és a normalized lowercase forma.
A string nem mindig „csak string”
Néha a valódi modell inkább:
- tömbscan,
- hashing,
- two pointers,
- vagy window maintenance.
A string csak a tároló.
Java-specifikus óvatosság
Mivel a String immutable, a nagy mennyiségű rekonstrukciót loopban óvatosan kezeld.
Ha csak count, index vagy normalizált összehasonlítás kell, ne allokálj többet a szükségesnél.
A magyarázási előny
Egy erős magyarázat itt egyszerű:
- milyen input-feltételezés van,
- milyen state-et őrzöl meg,
- miért tömb vagy map a jó domain-struktúra,
- és számít-e a sorrend.
Ez már önmagában sokkal erősebb, mint az, hogy „csak használtam egy mapet”.
8. Interjúkérdések
Q: Mikor jobb az int[26], mint a HashMap<Character, Integer>?
A: Akkor, ha a prompt garantálja a lowercase English letters vagy más kicsi fix alphabet használatát.
Q: Miért használnak sok stringes feladatban két passzt?
A: Mert az első passz countot vagy szerkezetet épít, a második pedig ebből előállítja a szükséges választ.
Q: Mi a kanonikus reprezentáció?
A: Olyan stabil forma, amely logikailag azonos inputokat ugyanúgy hasonlít össze, például rendezett string vagy count signature.
Q: Mi a leggyakoribb csendes bug a character-frequency feladatokban?
A: Az, hogy a megoldás kisebb karakterdomaint feltételez, mint amit a prompt valójában garantál.
Q: Miért ilyen fontos a normalizálás?
A: Mert a case, a punctuation és a whitespace szabályai gyakran megváltoztatják, mit jelent az, hogy két string „egyenlő”.
9. Fogalomtár
| Fogalom | Definíció |
|---|---|
| frequency counting | Annak követése, hogy egy karakter hányszor jelenik meg. |
| normalization | Az input közös, összehasonlítható formára hozása. |
| canonical representation | Stabil reprezentáció összehasonlításhoz vagy csoportosításhoz. |
| two-pass scan | Egy passz state-gyűjtésre, egy másik a válaszadásra. |
| character domain | Az a karakterkészlet, amelyet az input tartalmazhat. |
| immutable | Olyan érték, amely létrehozás után nem módosítható in-place. |
| signature | A string logikai tartalmát sűrítő reprezentáció. |
10. Cheatsheet
- Kérdezd meg, számít-e a sorrend.
- Kérdezd meg, milyen karakterdomain garantált.
- Ha a prompt kéri, compare előtt normalizálj.
- Fix lowercase-letter feladatokra jó az
int[26]. - Bizonytalan vagy széles domainre jobb a map.
- A two-pass scan gyakori és sokszor a legtisztább.
- A sorting valid, de a counting olcsóbb lehet.
- A frequency önmagában nem őriz sorrendérzékeny jelentést.
- Interjún mondd ki a feltételezéseidet.
- Sok stringes feladat valójában elrejtett counting probléma.
🎮 Játékok
10 kérdés