De ce contează?
Strângi cărți pe un raft până se umple. Când nu mai încape una nouă, scoți câteva de la celălalt capăt ca să faci loc. Capătul din dreapta tot adaugă, capătul din stânga eliberează doar cât e nevoie. Așa găsești cea mai lungă „felie” care respectă o regulă.
Problema
Vrei cea mai lungă secvență contiguă care respectă o proprietate. Exemplu concret: cel mai lung interval de elemente consecutive a căror sumă nu depășește S (toate elementele pozitive).
Spre deosebire de lungimea fixă, aici lungimea nu e dată — o căutăm. De-asta fereastra trebuie să se poată și întinde, și strânge.
Cheia: dacă o secvență respectă proprietatea, vrem s-o extindem; dacă o încalcă, o strângem din stânga până redevine validă. Niciun capăt nu dă vreodată înapoi.
Tehnica celor doi indici
Ținem st (stânga) și dr (dreapta), plus suma ferestrei curente:
- Avansează
dr, adăugândv[dr]la sumă. - Cât timp suma
> S, avanseazăstși scadev[st](strângi fereastra). - Fereastra
[st, dr]e acum validă — actualizează lungimea maximă.
Pas cu pas pe numere
Fie v = {2, 1, 3, 2, 1, 1} și S = 5. Căutăm cea mai lungă secvență cu sumă ≤ 5.
| v | 2 | 1 | 3 | 2 | 1 | 1 |
1 | 2 | 3 | 4 | 5 | 6 | |
st | dr |
| v | 2 | 1 | 3 | 2 | 1 | 1 |
1 | 2 | 3 | 4 | 5 | 6 | |
st | dr |
| v | 2 | 1 | 3 | 2 | 1 | 1 |
1 | 2 | 3 | 4 | 5 | 6 | |
st | dr |
Urmărind pas cu pas mișcarea capetelor:
dr=1: suma=2 valid -> lung=1
dr=2: suma=3 valid -> lung=2
dr=3: suma=6 >5 -> st=2, suma=4 valid -> lung=2
dr=4: suma=6 >5 -> st=3, suma=5 valid -> lung=2
dr=5: suma=6 >5 -> st=4, suma=4 valid -> lung=2
dr=6: suma=5 valid -> lung=3 (secventa {2,1,1})
raspuns: lungime maxima = 3Implementare C++
#include <iostream>
using namespace std;
int main() {
int v[] = {0, 2, 1, 3, 2, 1, 1}; // v[0] nefolosit
int n = 6, S = 5;
int st = 1, suma = 0, lungMax = 0;
for (int dr = 1; dr <= n; dr++) {
suma += v[dr]; // extindem in dreapta
while (suma > S) { // cat timp e invalid, strangem stanga
suma -= v[st];
st++;
}
int lung = dr - st + 1; // fereastra valida curenta
if (lung > lungMax) lungMax = lung;
}
cout << lungMax << "\n"; // 3
return 0;
}Deși există un while în interiorul lui for, st avansează în total cel mult de n ori pe tot parcursul — de-aici O(n).
Complexitate
| Metodă | Timp | Spațiu |
|---|---|---|
| Naiv (toate secvențele) | O(n²) | O(1) |
| Doi indici (fereastră variabilă) | O(n) | O(1) |
Greșeli frecvente de concurs:
- Crezi că e O(n²) din cauza
while-ului.stnu dă niciodată înapoi, deci suma pașilor lui e ≤ n. Bucla interioară nu repornește de la zero. - Tehnica cere proprietate monotonă. Fereastra variabilă cu doi indici merge când extinderea „strică” și strângerea „repară”. Pe elemente negative, suma nu mai e monotonă și metoda pică.
- Lungimea calculată greșit. Fereastra
[st, dr]aredr - st + 1elemente. Uitarea lui+1dă răspuns mai mic cu 1. - Nu actualizezi suma la strângere. Dacă avansezi
stfără să scaziv[st], suma rămâne umflată și fereastra pare mereu invalidă.
Vizualizare
Urmărește cele două capete cum se mișcă — dreapta extinde, stânga repară:
Gândește cele două capete ca pe o regulă: dreapta merge mereu înainte, stânga sare doar cât trebuie ca să restabilească proprietatea.
Recapitulare
- O secvență maximală are lungime variabilă: căutăm cea mai lungă care respectă o proprietate.
- Cu doi indici: extinzi în dreapta, strângi în stânga când proprietatea se strică.
- Total O(n) pentru că niciun capăt nu dă înapoi; merge doar pentru proprietăți monotone.