Subarray de suma máxima (Kadane)
Encontrar el tramo de un array cuya suma es la mayor. Kadane lo hace en una sola pasada decidiendo en cada posición si seguir con el tramo o empezar uno nuevo.
nivel intermedioTambién: Kadane, algoritmo de Kadane, maximum subarray, suma máxima contigua
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Subarray de suma máxima (Kadane)
Escribe un array con números positivos y negativos: en una sola pasada se decide en cada posición si seguir sumando o empezar de nuevo.
- el que se suma ahora
- fuera de juego
Paso 1
actual es la mejor suma de un tramo que ACABA en la posición que se mira; mejor, la mayor vista hasta ahora. Las dos empiezan con a[0] = -2.
1static int maxSubarray(int[] a) {
2 int actual = a[0], mejor = a[0]; // i = 0, a[i] = -2, actual = -2
3 for (int i = 1; i < a.length; i++) {
4 actual = Math.max(a[i], actual + a[i]);
5 mejor = Math.max(mejor, actual);
6 }
7 return mejor;
8}Variables
- i
- 0
- a[i]
- -2
- actual
- -2
- mejor
- -2
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Dado un array con números positivos y negativos (ganancias y pérdidas de cada día, por ejemplo), hay que encontrar el tramo de posiciones seguidas cuya suma es la mayor. En −2 1 −3 4 −1 2 1 −5 4, es 4 −1 2 1, que suma 6. Si todos fueran positivos, la respuesta sería el array entero; los negativos son los que lo complican.
La fuerza bruta prueba cada inicio y cada final: n²/2 tramos (n³ si cada suma se calcula desde cero). Kadane lo hace en una pasada con una sola pregunta por posición: la mejor suma de un tramo que acaba aquí, ¿sale de alargar el mejor tramo que acababa en la anterior, o de empezar de nuevo en esta posición? Alargar solo merece la pena si lo que se arrastra suma algo positivo.
Así, actual = max(a[i], actual + a[i]) es la mejor suma que acaba en i, y la respuesta es el máximo de todas ellas, que se lleva en otra variable. Para saber además dónde está el tramo, basta apuntar dónde se empezó de nuevo por última vez y, cada vez que la mejor suma mejora, copiar ese inicio y la posición actual como final.
Es programación dinámica en su forma más compacta: el subproblema es «el mejor tramo que acaba en i», depende solo del anterior y no hace falta tabla. Un detalle: si todos los números son negativos, el mejor tramo es el número más grande él solo (si el problema admite el tramo vacío, sería 0). La idea se extiende a matrices (rectángulo de suma máxima) fijando filas y aplicando Kadane a las columnas.
Cuándo usarlo
- Mejor racha en series temporales: beneficios, temperaturas sobre la media, puntuaciones.
- Detectar el periodo de mayor subida (con las diferencias entre días consecutivos).
- Bioinformática y procesado de señales: la zona con más «señal» de una secuencia.
- Como pieza de problemas más grandes: rectángulo de suma máxima en una matriz, array circular.
Cuándo no
- Si los elementos no tienen que ser seguidos: la mejor suma es la de todos los positivos.
- Si hay restricciones de longitud (tramos de exactamente k elementos): eso es una ventana deslizante.
- Si lo que se busca es un producto máximo: los negativos multiplicados cambian de signo y hace falta llevar también el mínimo.
Paso a paso
- Empezar. actual y mejor valen a[0]: el único tramo que acaba en la posición 0.
- Seguir o empezar de nuevo. En cada posición i: actual = max(a[i], actual + a[i]). Si gana a[i], el tramo empieza en i.
- Actualizar la mejor. Si actual supera a mejor, se guarda junto con el inicio y el final del tramo.
- Resultado. Al acabar, mejor es la suma máxima y sus posiciones son el tramo.
El código
Kadane con posiciones
Cuatro arrays, entre ellos uno de solo negativos: el mejor tramo es el número más grande él solo.
1import java.util.*;
2
3public class Main {
4 /** Kadane con posiciones: {suma, inicio, fin} del tramo de suma máxima. */
5 static int[] maxSubarray(int[] a) {
6 int actual = a[0], ini = 0;
7 int mejor = a[0], mejorIni = 0, mejorFin = 0;
8 for (int i = 1; i < a.length; i++) {
9 if (a[i] > actual + a[i]) { // lo anterior resta: se empieza de nuevo aquí
10 actual = a[i];
11 ini = i;
12 } else actual += a[i];
13 if (actual > mejor) {
14 mejor = actual;
15 mejorIni = ini;
16 mejorFin = i;
17 }
18 }
19 return new int[]{mejor, mejorIni, mejorFin};
20 }
21
22 public static void main(String[] args) {
23 int[][] casos = {{-2, 1, -3, 4, -1, 2, 1, -5, 4}, {5, -9, 6, -2, 3}, {-3, -1, -2}, {2, 3, -1, 4}};
24 for (int[] a : casos) {
25 int[] r = maxSubarray(a);
26 System.out.println(Arrays.toString(a) + " → suma " + r[0] + " en [" + r[1] + ".." + r[2] + "]");
27 }
28 }
29}def max_subarray(a):
"""Kadane con posiciones: (suma, inicio, fin) del tramo de suma máxima."""
actual, ini = a[0], 0
mejor, mejor_ini, mejor_fin = a[0], 0, 0
for i in range(1, len(a)):
if a[i] > actual + a[i]: # lo anterior resta: se empieza de nuevo aquí
actual, ini = a[i], i
else:
actual += a[i]
if actual > mejor:
mejor, mejor_ini, mejor_fin = actual, ini, i
return mejor, mejor_ini, mejor_fin
for a in [[-2, 1, -3, 4, -1, 2, 1, -5, 4], [5, -9, 6, -2, 3], [-3, -1, -2], [2, 3, -1, 4]]:
suma, ini, fin = max_subarray(a)
print(f"[{', '.join(map(str, a))}] → suma {suma} en [{ini}..{fin}]")/** Kadane con posiciones: [suma, inicio, fin] del tramo de suma máxima. */
function maxSubarray(a) {
let actual = a[0], ini = 0;
let mejor = a[0], mejorIni = 0, mejorFin = 0;
for (let i = 1; i < a.length; i++) {
if (a[i] > actual + a[i]) { // lo anterior resta: se empieza de nuevo aquí
actual = a[i];
ini = i;
} else actual += a[i];
if (actual > mejor) {
mejor = actual;
mejorIni = ini;
mejorFin = i;
}
}
return [mejor, mejorIni, mejorFin];
}
for (const a of [[-2, 1, -3, 4, -1, 2, 1, -5, 4], [5, -9, 6, -2, 3], [-3, -1, -2], [2, 3, -1, 4]]) {
const [suma, ini, fin] = maxSubarray(a);
console.log(`[${a.join(", ")}] → suma ${suma} en [${ini}..${fin}]`);
}using System;
class Program {
/// Kadane con posiciones: {suma, inicio, fin} del tramo de suma máxima.
static int[] MaxSubarray(int[] a) {
int actual = a[0], ini = 0;
int mejor = a[0], mejorIni = 0, mejorFin = 0;
for (int i = 1; i < a.Length; i++) {
if (a[i] > actual + a[i]) { // lo anterior resta: se empieza de nuevo aquí
actual = a[i];
ini = i;
} else actual += a[i];
if (actual > mejor) {
mejor = actual;
mejorIni = ini;
mejorFin = i;
}
}
return new[] { mejor, mejorIni, mejorFin };
}
static void Main() {
int[][] casos = { new[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 }, new[] { 5, -9, 6, -2, 3 }, new[] { -3, -1, -2 }, new[] { 2, 3, -1, 4 } };
foreach (int[] a in casos) {
int[] r = MaxSubarray(a);
Console.WriteLine("[" + string.Join(", ", a) + "] → suma " + r[0] + " en [" + r[1] + ".." + r[2] + "]");
}
}
}<?php
/** Kadane con posiciones: [suma, inicio, fin] del tramo de suma máxima. */
function maxSubarray(array $a): array {
$actual = $a[0];
$ini = 0;
$mejor = $a[0];
$mejorIni = 0;
$mejorFin = 0;
for ($i = 1; $i < count($a); $i++) {
if ($a[$i] > $actual + $a[$i]) { // lo anterior resta: se empieza de nuevo aquí
$actual = $a[$i];
$ini = $i;
} else $actual += $a[$i];
if ($actual > $mejor) {
$mejor = $actual;
$mejorIni = $ini;
$mejorFin = $i;
}
}
return [$mejor, $mejorIni, $mejorFin];
}
foreach ([[-2, 1, -3, 4, -1, 2, 1, -5, 4], [5, -9, 6, -2, 3], [-3, -1, -2], [2, 3, -1, 4]] as $a) {
[$suma, $ini, $fin] = maxSubarray($a);
echo "[" . implode(", ", $a) . "] → suma $suma en [$ini..$fin]\n";
}Salida al ejecutarlo (la misma en los 5 lenguajes)
[-2, 1, -3, 4, -1, 2, 1, -5, 4] → suma 6 en [3..6] [5, -9, 6, -2, 3] → suma 7 en [2..4] [-3, -1, -2] → suma -1 en [1..1] [2, 3, -1, 4] → suma 8 en [0..3]
Fuerza bruta frente a Kadane
2.000 números pseudoaleatorios (generados igual en cualquier lenguaje): la fuerza bruta hace dos millones de sumas y Kadane, 1.999 pasos. Mismo resultado.
1public class Main {
2 public static void main(String[] args) {
3 int n = 2000;
4 int[] a = new int[n];
5 int x = 7;
6 for (int i = 0; i < n; i++) { // números «aleatorios» que salen iguales en cualquier lenguaje
7 x = (x * 75 + 74) % 65537;
8 a[i] = x % 201 - 100;
9 }
10
11 long sumas = 0;
12 int mejorBruta = Integer.MIN_VALUE;
13 for (int i = 0; i < n; i++) { // fuerza bruta: cada inicio, alargando el final
14 int s = 0;
15 for (int j = i; j < n; j++) {
16 s += a[j];
17 sumas++;
18 mejorBruta = Math.max(mejorBruta, s);
19 }
20 }
21
22 int actual = a[0], mejor = a[0];
23 for (int i = 1; i < n; i++) {
24 actual = Math.max(a[i], actual + a[i]);
25 mejor = Math.max(mejor, actual);
26 }
27 System.out.println("Fuerza bruta: " + mejorBruta + " tras " + sumas + " sumas");
28 System.out.println("Kadane: " + mejor + " tras " + (n - 1) + " pasos");
29 }
30}n = 2000
a = []
x = 7
for i in range(n): # números «aleatorios» que salen iguales en cualquier lenguaje
x = (x * 75 + 74) % 65537
a.append(x % 201 - 100)
sumas = 0
mejor_bruta = -10**9
for i in range(n): # fuerza bruta: cada inicio, alargando el final
s = 0
for j in range(i, n):
s += a[j]
sumas += 1
if s > mejor_bruta:
mejor_bruta = s
actual = mejor = a[0]
for i in range(1, n):
actual = max(a[i], actual + a[i])
mejor = max(mejor, actual)
print(f"Fuerza bruta: {mejor_bruta} tras {sumas} sumas")
print(f"Kadane: {mejor} tras {n - 1} pasos")const n = 2000;
const a = new Array(n);
let x = 7;
for (let i = 0; i < n; i++) { // números «aleatorios» que salen iguales en cualquier lenguaje
x = (x * 75 + 74) % 65537;
a[i] = x % 201 - 100;
}
let sumas = 0;
let mejorBruta = -Infinity;
for (let i = 0; i < n; i++) { // fuerza bruta: cada inicio, alargando el final
let s = 0;
for (let j = i; j < n; j++) {
s += a[j];
sumas++;
mejorBruta = Math.max(mejorBruta, s);
}
}
let actual = a[0], mejor = a[0];
for (let i = 1; i < n; i++) {
actual = Math.max(a[i], actual + a[i]);
mejor = Math.max(mejor, actual);
}
console.log(`Fuerza bruta: ${mejorBruta} tras ${sumas} sumas`);
console.log(`Kadane: ${mejor} tras ${n - 1} pasos`);using System;
class Program {
static void Main() {
int n = 2000;
int[] a = new int[n];
int x = 7;
for (int i = 0; i < n; i++) { // números «aleatorios» que salen iguales en cualquier lenguaje
x = (x * 75 + 74) % 65537;
a[i] = x % 201 - 100;
}
long sumas = 0;
int mejorBruta = int.MinValue;
for (int i = 0; i < n; i++) { // fuerza bruta: cada inicio, alargando el final
int s = 0;
for (int j = i; j < n; j++) {
s += a[j];
sumas++;
mejorBruta = Math.Max(mejorBruta, s);
}
}
int actual = a[0], mejor = a[0];
for (int i = 1; i < n; i++) {
actual = Math.Max(a[i], actual + a[i]);
mejor = Math.Max(mejor, actual);
}
Console.WriteLine(quot;Fuerza bruta: {mejorBruta} tras {sumas} sumas");
Console.WriteLine(quot;Kadane: {mejor} tras {n - 1} pasos");
}
}<?php
$n = 2000;
$a = [];
$x = 7;
for ($i = 0; $i < $n; $i++) { // números «aleatorios» que salen iguales en cualquier lenguaje
$x = ($x * 75 + 74) % 65537;
$a[] = $x % 201 - 100;
}
$sumas = 0;
$mejorBruta = PHP_INT_MIN;
for ($i = 0; $i < $n; $i++) { // fuerza bruta: cada inicio, alargando el final
$s = 0;
for ($j = $i; $j < $n; $j++) {
$s += $a[$j];
$sumas++;
if ($s > $mejorBruta) $mejorBruta = $s;
}
}
$actual = $mejor = $a[0];
for ($i = 1; $i < $n; $i++) {
$actual = max($a[$i], $actual + $a[$i]);
$mejor = max($mejor, $actual);
}
echo "Fuerza bruta: $mejorBruta tras $sumas sumas\n";
echo "Kadane: $mejor tras " . ($n - 1) . " pasos\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
Fuerza bruta: 1848 tras 2001000 sumas Kadane: 1848 tras 1999 pasos
Traza: Kadane con el array del visualizador
| i | a[i] | actual | mejor | Tramo actual | Qué pasa |
|---|---|---|---|---|---|
| 0 | -2 | -2 | -2 | [0..0] | empieza |
| 1 | 1 | 1 | 1 | [1..1] | empieza de nuevo; nueva mejor |
| 2 | -3 | -2 | 1 | [1..2] | sigue |
| 3 | 4 | 4 | 4 | [3..3] | empieza de nuevo; nueva mejor |
| 4 | -1 | 3 | 4 | [3..4] | sigue |
| 5 | 2 | 5 | 5 | [3..5] | sigue; nueva mejor |
| 6 | 1 | 6 | 6 | [3..6] | sigue; nueva mejor |
| 7 | -5 | 1 | 6 | [3..7] | sigue |
| 8 | 4 | 5 | 6 | [3..8] | sigue |
En la posición 3 compensa empezar de nuevo (−2 + 4 = 2 < 4). Desde ahí, el tramo 4 −1 2 1 llega a 6 y nada lo supera.
Complejidad
| Método | Tiempo | Memoria |
|---|---|---|
| Fuerza bruta, sumando cada tramo | O(n³) | O(1) |
| Fuerza bruta alargando el final | O(n²) | O(1) |
| Divide y vencerás | O(n log n) | O(log n) |
| Kadane | O(n) | O(1) |
| Rectángulo máximo en una matriz (Kadane por columnas) | O(filas² · columnas) | O(columnas) |
Kadane no puede mejorarse: hay que mirar cada número al menos una vez.
- Mejor caso: O(n)
- Caso medio: O(n)
- Peor caso: O(n)
Kadane recorre el array una vez. Probar todas las parejas (inicio, fin) sería O(n²), y sumando cada tramo desde cero, O(n³). Las curvas grises son las demás clases, para comparar.
En la práctica
- Análisis financiero: el mejor periodo para haber comprado y vendido (con las diferencias diarias).
- Procesado de imágenes: la zona más brillante con el rectángulo de suma máxima.
- Bioinformática: regiones de una secuencia con más afinidad o puntuación.
- Es una de las preguntas más típicas de las entrevistas técnicas (LeetCode 53).
Errores típicos
- Empezar actual y mejor en 0: con todos los números negativos devuelve 0, que no es ningún tramo.
- Reiniciar cuando actual < 0 pero después de sumar a[i] (y no antes): se pierde el caso en que a[i] solo es mejor.
- Actualizar el inicio del mejor tramo cada vez que se empieza de nuevo: el mejor tramo puede ser uno anterior.
- Usar
intpara sumas muy grandes: con muchos números grandes hace faltalong. - Confundirlo con la subsecuencia: aquí los elementos tienen que ser consecutivos.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. La mejor racha
Cada línea son los resultados diarios de una tienda (ganancias positivas y pérdidas negativas). Escribe la mejor racha: los días seguidos con la mayor suma. El main lee cada línea y escribe el resultado: completa mejorRacha con Kadane, llevando también el inicio y el final.
- Entrada: una serie por línea, enteros separados por espacios.
- Salida:
Mejor racha: del día 4 al 7, +6(días desde 1). Si ninguna suma es positiva:Sin racha positiva: el mejor día es el 2 (-1). - Con empate, la racha que empieza antes y, si empiezan el mismo día, la más corta. Línea con otra cosa:
Línea no válida: «…».
Ejemplo
-2 1 -3 4 -1 2 1 -5 4 3 -1 2
Mejor racha: del día 4 al 7, +6 Mejor racha: del día 1 al 3, +4
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 /** {suma, inicio, fin} del tramo de suma máxima (posiciones desde 0). Con empate, el que empieza antes y,
5 si empiezan igual, el más corto: es justo lo que da Kadane comparando con > estricto. */
6 static int[] mejorRacha(int[] a) {
7 int actual = a[0], ini = 0, mejor = a[0], mi = 0, mf = 0;
8 for (int i = 1; i < a.length; i++) {
9 if (a[i] > actual + a[i]) {
10 actual = a[i];
11 ini = i;
12 } else actual += a[i];
13 if (actual > mejor) {
14 mejor = actual;
15 mi = ini;
16 mf = i;
17 }
18 }
19 return new int[]{mejor, mi, mf};
20 }
21
22 public static void main(String[] args) {
23 Scanner sc = new Scanner(System.in);
24 while (sc.hasNextLine()) {
25 String linea = sc.nextLine().trim();
26 if (linea.isEmpty()) continue;
27 if (!linea.matches("-?\\d{1,6}(\\s+-?\\d{1,6})*")) {
28 System.out.println("Línea no válida: «" + linea + "»");
29 continue;
30 }
31 int[] a = Arrays.stream(linea.split("\\s+")).mapToInt(Integer::parseInt).toArray();
32 int[] r = mejorRacha(a);
33 if (r[0] <= 0) System.out.println("Sin racha positiva: el mejor día es el " + (r[1] + 1) + " (" + r[0] + ")");
34 else System.out.println("Mejor racha: del día " + (r[1] + 1) + " al " + (r[2] + 1) + ", +" + r[0]);
35 }
36 }
37}Los empates se resuelven solos con las comparaciones estrictas: alargar en vez de reiniciar conserva el inicio más temprano, y no sustituir la mejor con un empate conserva el final más temprano.
El mejor día suelto (lo que hace el código inicial) es la respuesta solo cuando todos los números son negativos.
2. El rectángulo de suma máxima
Una matriz de enteros (por ejemplo, la rentabilidad de cada parcela de un terreno). Encuentra la mayor suma de un rectángulo de casillas seguidas. Probar todos los rectángulos es O(n⁶) sumando cada uno, demasiado para 80 × 80. La idea: fija la fila de arriba y la de abajo, suma cada columna entre ellas y aplica Kadane a esas sumas. Completa submatrizMaxima.
- Entrada: una fila de la matriz por línea, enteros separados por espacios, todas con la misma cantidad.
- Salida:
Suma máxima: 29. - Errores:
Fila no válida: «…»,La fila 3 tiene 4 números y la primera 5,Matriz vacía.
Ejemplo
1 2 -1 -4 -20 -8 -3 4 2 1 3 8 10 1 3 -4 -1 1 7 -6
Suma máxima: 29
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 /** La mayor suma de un rectángulo de la matriz. Se fija la fila de arriba, se va bajando la de abajo
5 acumulando cada columna, y Kadane busca el mejor tramo de columnas: O(filas² · columnas). */
6 static long submatrizMaxima(int[][] m) {
7 int filas = m.length, cols = m[0].length;
8 long mejor = Long.MIN_VALUE;
9 for (int arriba = 0; arriba < filas; arriba++) {
10 long[] col = new long[cols];
11 for (int abajo = arriba; abajo < filas; abajo++) {
12 for (int c = 0; c < cols; c++) col[c] += m[abajo][c];
13 long actual = col[0];
14 mejor = Math.max(mejor, actual);
15 for (int c = 1; c < cols; c++) {
16 actual = Math.max(col[c], actual + col[c]);
17 mejor = Math.max(mejor, actual);
18 }
19 }
20 }
21 return mejor;
22 }
23
24 public static void main(String[] args) {
25 Scanner sc = new Scanner(System.in);
26 List<int[]> filas = new ArrayList<>();
27 while (sc.hasNextLine()) {
28 String linea = sc.nextLine().trim();
29 if (linea.isEmpty()) continue;
30 if (!linea.matches("-?\\d{1,6}(\\s+-?\\d{1,6})*")) {
31 System.out.println("Fila no válida: «" + linea + "»");
32 return;
33 }
34 int[] f = Arrays.stream(linea.split("\\s+")).mapToInt(Integer::parseInt).toArray();
35 if (!filas.isEmpty() && f.length != filas.get(0).length) {
36 System.out.println("La fila " + (filas.size() + 1) + " tiene " + f.length + " números y la primera " + filas.get(0).length);
37 return;
38 }
39 filas.add(f);
40 }
41 if (filas.isEmpty()) {
42 System.out.println("Matriz vacía");
43 return;
44 }
45 System.out.println("Suma máxima: " + submatrizMaxima(filas.toArray(new int[0][])));
46 }
47}Fijar dos filas convierte el problema 2D en uno 1D: un rectángulo entre esas filas es un tramo de columnas. Con 80 × 80 son 3.240 parejas de filas por 80 columnas, unas 260.000 operaciones.
Para matrices más anchas que altas conviene fijar columnas y aplicar Kadane a las filas: el coste es O(min² · max).
Test
Test: Subarray de suma máxima (Kadane)
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.¿Cuál es la suma máxima de un tramo de 2 −3 4 −1 2?
2.¿Qué representa la variable actual en Kadane?
3.¿Cuándo empieza Kadane un tramo nuevo en la posición i?
4.Si todos los números son negativos, ¿qué devuelve Kadane empezando con a[0]?
5.¿Qué coste tiene Kadane?