Subsecvența cu suma maximă (Kadane 1D)
Algoritmul lui Kadane rezolvă una dintre cele mai clasice probleme pe vectori:
dintre toate secvențele de elemente consecutive, care are suma maximă?
Această problemă apare foarte des în concursuri și este importantă și pentru lecția despre Kadane 2D, unde ideea se extinde la matrici.
Problema
Avem un vector v[1..n] cu numere pozitive și
negative. Vrem să găsim:
- suma maximă a unei subsecvențe consecutive
O subsecvență consecutivă înseamnă un interval de forma:
v[st], v[st + 1], ..., v[dr]unde 1 <= st <= dr <= n.
Exemplu
Vector: 2 -3 5 1 -2 4 -1Subsecvența optimă este:
5 1 -2 4Suma ei este:
5 + 1 + (-2) + 4 = 8Deci răspunsul este 8.
Forța brută
Am putea încerca toate intervalele [st, dr]:
- alegem
st - alegem
dr - calculăm suma pe interval
int maxim = v[1];
for (int st = 1; st <= n; st++) {
int suma = 0;
for (int dr = st; dr <= n; dr++) {
suma = suma + v[dr];
if (suma > maxim)
maxim = suma;
}
}Complexitate: O(n²).
Pentru n mare, este prea lent.
Ideea lui Kadane
Parcurgem vectorul de la stânga la dreapta și menținem:
sumaCurenta= suma celei mai bune secvențe care se termină în poziția curentăsumaMax= cea mai bună sumă găsită până acum
La fiecare element v[i], avem două opțiuni:
- continuăm secvența anterioară
- începem o secvență nouă din
v[i]
Dacă suma acumulată până acum este negativă, ea nu ne ajută. Mai bine o abandonăm și pornim din nou.
Varianta clasică
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int v[100001];
int n;
int main()
{
fin >> n;
for (int i = 1; i <= n; i++)
fin >> v[i];
int sumaCurenta = 0;
int sumaMax = v[1];
for (int i = 1; i <= n; i++) {
sumaCurenta = sumaCurenta + v[i];
if (sumaCurenta > sumaMax)
sumaMax = sumaCurenta;
if (sumaCurenta < 0)
sumaCurenta = 0;
}
fout << sumaMax;
return 0;
}date.in:
7
2 -3 5 1 -2 4 -1date.out:
8Pas cu pas
Pentru vectorul:
2 -3 5 1 -2 4 -1| i | v[i] | sumaCurenta | sumaMax |
|---|---|---|---|
| 1 | 2 | 2 | 2 |
| 2 | -3 | -1 -> 0 | 2 |
| 3 | 5 | 5 | 5 |
| 4 | 1 | 6 | 6 |
| 5 | -2 | 4 | 6 |
| 6 | 4 | 8 | 8 |
| 7 | -1 | 7 | 8 |
Răspuns final: 8.
De ce resetăm suma la 0?
Dacă sumaCurenta < 0, atunci orice secvență
viitoare care ar continua de aici ar fi mai slabă decât una care
începe direct de la următorul element.
Exemplu:
sumaCurenta = -5
următorul element = 7Dacă continuăm, obținem:
-5 + 7 = 2Dar dacă începem direct cu 7, obținem:
7Clar este mai bine să aruncăm suma negativă.
Formularea matematică
Kadane calculează:
bestEnd[i] = max(v[i], bestEnd[i-1] + v[i])
adică cea mai bună subsecvență care se termină exact în
i.
Răspunsul final este:
max(bestEnd[i])
Varianta cu sumaCurenta și reset la
0 este o implementare mai compactă a aceleiași
idei.
Cazul cu toate elementele negative
Acesta este cazul unde mulți greșesc.
Exemplu:
-8 -3 -5 -10Răspunsul corect nu este 0, ci -3,
pentru că subsecvența trebuie să fie
nevidă.
De aceea inițializăm:
int sumaMax = v[1];nu:
int sumaMax = 0; // GREȘITVarianta robustă
O variantă foarte clară și sigură este:
int sumaCurenta = v[1];
int sumaMax = v[1];
for (int i = 2; i <= n; i++) {
if (sumaCurenta + v[i] > v[i])
sumaCurenta = sumaCurenta + v[i];
else
sumaCurenta = v[i];
if (sumaCurenta > sumaMax)
sumaMax = sumaCurenta;
}Aceasta spune explicit:
- ori continui secvența anterioară
- ori pornesc una nouă din
v[i]
Este echivalentă cu varianta clasică.
Aflarea pozițiilor intervalului optim
Uneori nu ni se cere doar suma maximă, ci și intervalul
[st, dr].
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int v[100001];
int n;
int main()
{
fin >> n;
for (int i = 1; i <= n; i++)
fin >> v[i];
int sumaCurenta = v[1];
int sumaMax = v[1];
int stCurent = 1;
int stBest = 1, drBest = 1;
for (int i = 2; i <= n; i++) {
if (sumaCurenta + v[i] < v[i]) {
sumaCurenta = v[i];
stCurent = i;
} else {
sumaCurenta = sumaCurenta + v[i];
}
if (sumaCurenta > sumaMax) {
sumaMax = sumaCurenta;
stBest = stCurent;
drBest = i;
}
}
fout << "Suma maxima: " << sumaMax << "\n";
fout << "Interval: [" << stBest << ", " << drBest << "]";
return 0;
}Pentru:
2 -3 5 1 -2 4 -1obținem:
Suma maxima: 8
Interval: [3, 6]Exemplu complet
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int v[100001];
int n;
int main()
{
fin >> n;
for (int i = 1; i <= n; i++)
fin >> v[i];
int sumaCurenta = 0;
int sumaMax = v[1];
for (int i = 1; i <= n; i++) {
sumaCurenta = sumaCurenta + v[i];
if (sumaCurenta > sumaMax)
sumaMax = sumaCurenta;
if (sumaCurenta < 0)
sumaCurenta = 0;
}
fout << sumaMax << "\n";
return 0;
}date.in:
8
-2 1 -3 4 -1 2 1 -5date.out:
6Subsecvența optimă este:
4 -1 2 1cu suma 6.
Complexitate
- Timp: O(n)
- Memorie suplimentară: O(1)
Parcurgem vectorul o singură dată.
Comparație
| Metodă | Complexitate |
|---|---|
| Forță brută | O(n²) |
| Kadane | O(n) |
Kadane este optim pentru această problemă.
Legătura cu Kadane 2D
În varianta pe matrici, alegem două linii și comprimăm coloanele într-un vector. Apoi aplicăm exact acest algoritm pe acel vector.
De aceea, Kadane 1D este baza pentru Kadane 2D.
Greșeli frecvente
1. Inițializarea cu 0
int sumaMax = 0; // GREȘITDacă toate valorile sunt negative, răspunsul devine greșit.
Corect:
int sumaMax = v[1];2. Considerarea subsecvenței vide
În problema clasică, subsecvența trebuie să fie nevidă. Nu avem voie să alegem „nimic” doar ca să obținem suma 0.
3. Confuzia dintre „subsecvență” și „subșir”
La Kadane, elementele trebuie să fie consecutive.
Exemplu:
1 -100 2 3Nu avem voie să alegem 1 2 3, pentru că nu sunt
consecutive.
Subsecvențe valide:
1-1002 31 -100 2
4. Uitarea pozițiilor
Dacă problema cere și intervalul optim, nu este suficient să calculăm doar suma. Trebuie să memorăm și:
- unde începe secvența curentă
- unde începe și se termină secvența optimă
Ce să reții
- Kadane găsește subsecvența consecutivă cu suma maximă.
- Ideea: dacă suma curentă devine negativă, o abandonăm.
- Complexitate: O(n).
- Inițializarea trebuie făcută cu
v[1], nu cu0. - Algoritmul funcționează și când toate valorile sunt negative.
- Poate fi extins pentru a afla și intervalul
[st, dr]. - Este baza pentru Kadane 2D.