Prefix sum és különbség gondolkodás
LeetCode DSA témakör: prefix sum, range queries, subarray sums, difference arrays
A prefix sum arra tanít, hogyan fizess egyszer egy előfeldolgozási költséget azért, hogy a későbbi range kérdések olcsók legyenek. A difference array ennek a duális ötlete: a range update hatását rögzíted olcsón most, a végső értékeket pedig később materializálod.
1. Definíció
Miről szól ez a téma?
Ez a téma arról szól, hogyan alakítod a sok ismétlődő range munkát inkrementális state-té.
Ahelyett, hogy minden alkalommal újraszámolnád egy részintervallum összegét, előre felépítesz elég szerkezetet ahhoz, hogy a range eredmény gyorsan levezethető legyen.
Ugyanez a gondolkodás az update-ekre is kiterjed.
Ahelyett, hogy egy range update minden elemét módosítanád, csak azt rögzíted, hol kezdődik a hatás és hol szűnik meg.
A két fő ötlet
Ez a fejezet két összekapcsolt mintát fog össze:
- prefix sum gyors range querykhez,
- difference array gyors range update-ekhez.
Miért fontos ez interjún?
Sok range feladat úgy van megfogalmazva, hogy a brute-force megoldás egy belső loopot hív meg újra és újra.
Ez kis példákon működhet, de nagy constrainteknél szétesik.
A prefix gondolkodás átfordítja a modellt a „minden range-et külön kiszámolok” szemléletből a „korábbi felhalmozott munkát újrahasznosítom” szemléletbe.
Mi NEM ez a téma?
Ez még nem segment tree, Fenwick tree vagy lazy propagation.
Ez a korábbi, interjúkban sokkal gyakoribb réteg, ahol ilyen eszközökkel dolgozol:
- running sum,
- prefix tábla,
- prefix értékekre épülő hash map,
- difference marker.
Felismerési jelek
A témának be kell kapcsolnia, ha a prompt ilyen dolgokról beszél:
- range sum query,
- sok ismételt subarray sum,
- valamilyen tulajdonságú subarray-ek számlálása,
- vagy sok range increment alkalmazása.
2. Alapfogalmak
Running sum
A running sum az a total, amelyet balról jobbra haladva fokozatosan felépítesz.
Ez a prefix sum alapja.
Prefix sum
A prefix sum az i indexnél a kezdeti szakasz összegét tárolja egy adott pontig.
Egy gyakori konvenció:
prefix[0] = 0,prefix[i + 1] = prefix[i] + nums[i].
Ez a formula tisztább range képleteket ad.
Range sum prefixből
Ha van prefix tömböd, akkor a [left, right] intervallum összege:
$$ \text{rangeSum}(left, right) = prefix[right + 1] - prefix[left] $$
Ez megszünteti a belső loopot.
Előfeldolgozás versus lekérdezési költség
A prefix sum egy egyszeri előfeldolgozási passzt cserél olcsóbb ismételt querykre.
Sokszor pontosan ezt akarja a feladat.
Prefix state mint modell, nem csak mint tömb
A prefix értékeket nem mindig csak közvetlen range sum queryhez tárolod.
Néha azt követed, egy prefix érték hányszor jelent meg eddig.
Ez a kulcs az ilyen feladatokhoz:
- subarray sum equals
k, - zero-sum subarray count,
- balanced prefix minták.
Difference array
A difference array a szomszédos pozíciók közti változást tárolja.
Ahelyett, hogy egy [left, right] range increment minden elemére ráírnád az update-et, megteheted, hogy:
- hozzáadod a
valueértéket aleftindexnél, - levonod a
valueértéket aright + 1indexnél, ha létezik, - majd egy running summal visszaépíted a végső tömböt.
Miért működik a difference array?
Mert az increment a bal határtól kezd hatni, és közvetlenül a jobb határ után megszűnik.
Elég ezeket a boundary pontokat rögzíteni.
Inclusive boundaryk számítanak
A range képletek gyakran off-by-one hibák miatt romlanak el.
Mindig egyértelműen meg kell mondanod, hogy a tartomány:
- inclusive,
- exclusive,
- vagy half-open.
A negatív számok számítanak
A sliding window technikák gyakran nemnegatív értékekre támaszkodnak.
A prefix-sum-plus-map megoldások nem igénylik ugyanezt a feltételezést.
Ez nagyon fontos különbség.
Felismerő mondat
Erős ösztön lehet ez:
- „Ha ugyanazokat az átfedő range-eket újraszámolom, prefix sum kellhet.”
- „Ha ugyanazokat az átfedő update-eket újraalkalmazom, difference array kellhet.”
3. Gyakorlati használat
Gyors range sum queryk
Ha sok sum queryt kell megválaszolni egy nem módosuló tömbön, a prefix sum általában az első eszköz, amit érdemes kipróbálni.
Target összegű subarray-ek számlálása
Ha a prompt azt kérdezi, hány olyan subarray van, amelynek összege k, gondolj prefix sumra és egy mapre a korábban látott prefix értékekkel.
A kulcsazonosság:
$$ currentPrefix - earlierPrefix = k $$
Tehát ha a jelenlegi prefix s, akkor azt kell tudnod, hányszor fordult elő korábban az s - k érték.
Sok range increment
Ha a prompt sok ilyen műveletet kér, hogy „adj hozzá value-t minden indexhez left és right között”, akkor a közvetlen per-range loop túl lassú lehet.
Itt lesz értékes a difference gondolkodás.
Intuíció építése
A prefix sum arról szól, hogy megkérdezed:
- mekkora total gyűlt össze eddig,
- és két total különbségéből hogyan kapom meg a kívánt range-et.
A difference array arról szól, hogy megkérdezed:
- hol kezdődik egy hatás,
- hol ér véget,
- és később vissza tudom-e építeni a teljes eredményt.
Interjús magyarázati minta
Egy tiszta magyarázat gyakran így hangzik:
- „A naiv megoldás átfedő munkát számol újra.”
- „A queryk vagy update-ek közös szerkezetet osztanak meg.”
- „Ezt az ismétlődő munkát át tudom tenni egy előfeldolgozási passzba.”
- „Így minden query vagy minden update sokkal olcsóbb lesz.”
Mikor ne erőltesd vakon?
Ne erőltesd a prefix sumot, ha:
- a művelet nem additív,
- az update és a query is dinamikusan változik bonyolultabban,
- vagy a feladatot más technika kezeli jobban.
De a range-alapú interjúalapoknál ezek rendkívül gyakori minták.
4. Kódpéldák
Alap példa — prefix sum építése range querykhez
public class RangeSumQuery {
private final int[] prefix;
public RangeSumQuery(int[] nums) {
this.prefix = new int[nums.length + 1];
for (int index = 0; index < nums.length; index++) {
prefix[index + 1] = prefix[index] + nums[index];
}
}
public int sumRange(int left, int right) {
return prefix[right + 1] - prefix[left];
}
}
Miért működik:
- az előfeldolgozás lineáris,
- minden query konstans idejű,
- és a formula tiszta, ha
prefix[0] = 0.
Köztes példa — subarray sum equals `k`
import java.util.HashMap;
import java.util.Map;
public class SubarraySumEqualsK {
public static int countSubarrays(int[] nums, int target) {
Map<Integer, Integer> seen = new HashMap<>();
seen.put(0, 1);
int prefix = 0;
int result = 0;
for (int value : nums) {
prefix += value;
result += seen.getOrDefault(prefix - target, 0);
seen.put(prefix, seen.getOrDefault(prefix, 0) + 1);
}
return result;
}
}
Ez az egyik legfontosabb prefix-sumos interjúminta.
Nem csak a prefix értékeket tárolod.
Azt is tárolod, egy prefix érték hányszor fordult elő.
Köztes példa — difference array range incrementhez
public class RangeIncrement {
public static int[] applyUpdates(int length, int[][] updates) {
int[] diff = new int[length];
for (int[] update : updates) {
int left = update[0];
int right = update[1];
int value = update[2];
diff[left] += value;
if (right + 1 < length) {
diff[right + 1] -= value;
}
}
int[] result = new int[length];
int running = 0;
for (int index = 0; index < length; index++) {
running += diff[index];
result[index] = running;
}
return result;
}
}
Az update költsége konstans lesz műveletenként, és a végső materializálás egyetlen passz.
Haladó példa — kapacitásellenőrzés sok út után
public class CarPoolingCheck {
public static boolean canCarryAllTrips(int[][] trips, int capacity) {
int[] diff = new int[1001];
for (int[] trip : trips) {
int passengers = trip[0];
int from = trip[1];
int to = trip[2];
diff[from] += passengers;
diff[to] -= passengers;
}
int currentLoad = 0;
for (int change : diff) {
currentLoad += change;
if (currentLoad > capacity) {
return false;
}
}
return true;
}
}
Ez remek példa arra, hogy difference gondolkodással inkább a change pointok számítanak, nem minden egyes index a range-en belül.
Gyakori hiba — off-by-one prefix formula
public class BrokenRangeSum {
public static int sumRange(int[] prefix, int left, int right) {
return prefix[right] - prefix[left];
}
}
Ha a prefix[i] azt jelenti, hogy az i index előtti elemek összege, akkor ez a formula hibás inclusive [left, right] range-re.
A helyes formula ezzel a konvencióval:
return prefix[right + 1] - prefix[left];
5. Trade-offok
| Szempont | Részletek |
|---|---|
| ⚡ Performance | A prefix előfeldolgozás lineáris, és az ismételt range queryket konstans idejűvé teheti. A difference array minden range update-et konstans idejűvé tesz a végső visszaépítés előtt. |
| 💾 Memory | Extra memóriát fizetsz a prefix vagy difference struktúráért, hogy megszűnjön az ismételt munka. |
| 🔧 Maintainability | A képletek elegánsak, ha az indexelési konvenció tiszta, de a homályos boundaryk könnyen review-problémává válnak. |
| 🔄 Flexibility | Additív range logikára kiváló, de nem univerzális válasz minden dinamikus query/update problémára. |
Performance trade-off: Egyszer fizetsz, hogy sokszor spórolj.
Memory trade-off: Az extra tömbök szinte mindig megérik, ha sok az ismétlődő range munka.
Maintainability trade-off: Az olyan konvenciók, mint a prefix[0] = 0, sokkal biztonságosabbá teszik a kódot.
Flexibility trade-off: A prefix és difference minták erősek, de nem helyettesítenek minden advanced range adatstruktúrát.
6. Gyakori hibák
Brute-force range loopok ismételt használata
Ez tipikusan arra utal, hogy elmaradt a shared work felismerése.Rossz prefix indexelési konvenció
Sok hiba abból jön, hogy nem konzisztens, mit jelent aprefix[i].A kezdeti
0 -> 1base case elfelejtése prefix-map feladatnál
Ez kritikus az index0-nál kezdődő subarray-ekhez.Sliding window használata negatív számok mellett
Ilyenkor sokszor prefix-sum plus map megoldás kell inkább.A difference update leállításának elfelejtése a
right + 1helyen
Enélkül a hatás túl sokáig marad érvényben.Kilépés a tömbhatáron túlra
Aright + 1használatát boundary checkkel kell védeni.Query logika és update logika összekeverése
A prefix sum gyors querykre jó; a difference array gyors update-ekre jó.
7. Mélyebb összefüggések
A mélyebb mentális váltás
Itt az igazi erő nem maga a formula.
Hanem az a váltás, hogy a közvetlen munkáról átállsz az újrahasznosítható felhalmozott state-re.
Ha átfedő subarray-eket vagy átfedő update-eket látsz, akkor azt kell kérdezned, hogy a korábbi munka ismételhető-e helyett újrahasznosítható-e.
A prefix sum nem csak összeadásról szól
A beginnerszintű benyomás gyakran az, hogy a prefix sum „csak kumulatív összeadás”.
A valódi trükk azonban két felhalmozott state kivonása egy köztes range izolálására.
Ezért ilyen újrahasznosítható a módszer.
A prefix map tovább viszi az ötletet
Amikor a prefix sumot map-pel párosítod, már nem csak explicit range-ekben gondolkodsz, hanem a korábbi és jelenlegi felhalmozott state közti kapcsolatokban.
Ezért lesz sokkal könnyebb a subarray sum equals k típusú feladat.
A difference array ennek a tükörképe
A prefix sum sok queryt válaszol meg egy felhalmozási passz után.
A difference array sok update-et nyel el előbb, majd egy felhalmozási passzal adja vissza a végső értékeket.
Ezt a szimmetriát érdemes megjegyezni.
Miért érződnek ezek „okosnak” interjún?
Azért, mert az ismételt lokális munkát kompakt globális modellel helyettesítik.
Az interjúztatók ezt szeretik, mert pattern recognitiont mutat, nem csak implementációt.
Java-specifikus megjegyzések
Java-ban a tömbök különösen tisztává teszik ezeket a mintákat.
A fő veszély nem a szintaxis, hanem az indexelési fegyelem.
Ha világosan definiálod, mit jelent a prefix[i], és hogyan értelmezed a range boundarykat, az implementáció egyenes lesz.
8. Interjúkérdések
Q: Miért jó a prefix[0] = 0?
A: Tisztábbá teszi a range képleteket, és természetesen támogatja az index 0-tól induló intervallumokat.
Q: Mi a subarray sum equals k fő ötlete?
A: A jelenlegi prefix sum mellé azt nézed, hányszor fordult elő korábban a currentPrefix - k érték.
Q: Miért fontos a seen.put(0, 1) base case?
A: Mert kezeli azokat a subarray-eket, amelyek index 0-nál indulnak és már önmagukban kiadják a targetet.
Q: Mi a difference array alapötlete?
A: Azt rögzíti, hol kezdődik és hol szűnik meg egy range update hatása, majd running summal visszaépíti a végső értékeket.
Q: Mi a gyakori bugforrás ebben a témában?
A: Az off-by-one indexelés és a nem tisztázott boundary konvenció.
9. Fogalomtár
| Fogalom | Definíció |
|---|---|
| prefix sum | Felhalmozott összeg a kezdetektől egy adott pontig. |
| running sum | Balról jobbra haladva fokozatosan épített összeg. |
| range query | Egy összefüggő intervallumra vonatkozó kérdés, például left-től right-ig vett összeg. |
| difference array | Olyan struktúra, amely range update-ek boundary változásait tárolja. |
| preprocessing | Előre elvégzett munka, amely a későbbi műveleteket olcsóbbá teszi. |
| materialization | A végső értékek visszaépítése tömörített state-ből. |
| off-by-one error | Olyan bug, amelyben egy boundary egy indexszel elcsúszik. |
10. Cheatsheet
- Az ismételt range sum gyakran prefix sumot jelez.
- Az ismételt range increment gyakran difference arrayt jelez.
- Tiszta prefix konvenció a
prefix[0] = 0. - Inclusive range sum gyakran
prefix[right + 1] - prefix[left]. subarray sum equals kfeladatnál acurrentPrefix - kértéket keresd a mapben.- Prefix map esetén gyakran kell a
0 -> 1induló állapot. - Difference update
left-nél indul ésright + 1-nél áll meg. - A
right + 1boundaryt mindig védd. - Negatív számok gyakran megtörik az egyszerű sliding window intuíciót.
- A shared work felismerése az előfeldolgozás legfontosabb jele.
🎮 Játékok
10 kérdés