Buscar el subarreglo con suma máxima/mínima
Aquí consideramos el problema de hallar un subarreglo con suma máxima, así como algunas de sus variantes (incluyendo el algoritmo para resolver este problema online).
Enunciado del problema
Se da un arreglo de números . Se pide hallar un subarreglo con la suma máxima:
Por ejemplo, si todos los enteros del arreglo fueran no negativos, entonces la respuesta sería el arreglo mismo. Sin embargo, la solución no es trivial cuando el arreglo puede contener tanto números positivos como negativos.
Está claro que el problema de hallar el subarreglo mínimo es esencialmente el mismo: solo hay que cambiar los signos de todos los números.
Algoritmo 1
Aquí consideramos un algoritmo casi obvio. (A continuación veremos otro algoritmo, que es un poco más difícil de idear, pero su implementación es aún más corta.)
Descripción del algoritmo
El algoritmo es muy sencillo.
Introducimos por conveniencia la notación: . Es decir, el arreglo es un arreglo de sumas parciales del arreglo . También, ponemos .
Iteremos ahora sobre el índice , y aprendamos a hallar rápidamente el óptimo para cada valor actual , en el que se alcanza la suma máxima en el subarreglo .
Formalmente, esto significa que para el actual necesitamos hallar un (que no exceda ), de modo que el valor de sea máximo. Tras una transformación trivial, podemos ver que necesitamos hallar en el arreglo un mínimo en el segmento .
De aquí obtenemos de inmediato una solución: simplemente guardamos dónde está el mínimo actual en el arreglo . Usando este mínimo, hallamos el índice óptimo actual en , y al pasar del índice actual al siguiente, simplemente actualizamos este mínimo.
Obviamente, este algoritmo funciona en y es asintóticamente óptimo.
Implementación
Para implementarlo, ni siquiera necesitamos guardar explícitamente un arreglo de sumas parciales : solo necesitaremos el elemento actual de él.
La implementación se da en arreglos con indexación desde cero, no en numeración desde uno como se describió arriba.
Primero damos una solución que halla una respuesta numérica simple sin hallar los índices del segmento deseado:
int ans = a[0], sum = 0, min_sum = 0;
for (int r = 0; r < n; ++r) {
sum += a[r];
ans = max(ans, sum - min_sum);
min_sum = min(min_sum, sum);
}Ahora damos una versión completa de la solución, que además también halla los límites del segmento deseado:
int ans = a[0], ans_l = 0, ans_r = 0;
int sum = 0, min_sum = 0, min_pos = -1;
for (int r = 0; r < n; ++r) {
sum += a[r];
int cur = sum - min_sum;
if (cur > ans) {
ans = cur;
ans_l = min_pos + 1;
ans_r = r;
}
if (sum < min_sum) {
min_sum = sum;
min_pos = r;
}
}Algoritmo 2
Aquí consideramos un algoritmo distinto. Es un poco más difícil de entender, pero es más elegante que el de arriba, y su implementación es un poco más corta. Este algoritmo lo propuso Jay Kadane en 1984.
Descripción del algoritmo
El algoritmo en sí es el siguiente. Recorremos el arreglo y acumulamos la suma parcial actual en alguna variable . Si en algún momento es negativa, simplemente asignamos . Se afirma que el máximo de todos los valores que se asignan a la variable durante el algoritmo será la respuesta al problema.
Demostración:
Consideremos el primer índice cuando la suma se vuelve negativa. Esto significa que empezando con una suma parcial cero, eventualmente obtenemos una suma parcial negativa: así que todo este prefijo del arreglo, así como cualquier sufijo, tiene suma negativa. Por lo tanto, este subarreglo nunca contribuye a la suma parcial de ningún subarreglo del que sea prefijo, y se puede simplemente descartar.
Sin embargo, esto no basta para demostrar el algoritmo. En el algoritmo, de hecho estamos limitados a hallar la respuesta solo en aquellos segmentos que empiezan inmediatamente después de los lugares en que ocurrió .
Pero, de hecho, consideremos un segmento arbitrario , y no está en tal posición “crítica” (es decir, , donde es la última tal posición en la que ). Como la última posición crítica es estrictamente anterior a , resulta que la suma de es no negativa. Esto significa que al mover a la posición , aumentaremos la respuesta o, en casos extremos, no la cambiaremos.
De una u otra forma, resulta que al buscar una respuesta, uno puede limitarse solo a segmentos que empiezan inmediatamente después de las posiciones en las que apareció . Esto demuestra que el algoritmo es correcto.
Implementación
Como en el algoritmo 1, primero dimos una implementación simplificada que busca solo una respuesta numérica sin hallar los límites del segmento deseado:
int ans = a[0], sum = 0;
for (int r = 0; r < n; ++r) {
sum += a[r];
ans = max(ans, sum);
sum = max(sum, 0);
}Una solución completa, que mantiene los índices de los límites del segmento correspondiente:
int ans = a[0], ans_l = 0, ans_r = 0;
int sum = 0, minus_pos = -1;
for (int r = 0; r < n; ++r) {
sum += a[r];
if (sum > ans) {
ans = sum;
ans_l = minus_pos + 1;
ans_r = r;
}
if (sum < 0) {
sum = 0;
minus_pos = r;
}
}Tareas relacionadas
Hallar el subarreglo máximo/mínimo con restricciones
Si la condición del problema impone restricciones adicionales sobre el segmento pedido (por ejemplo, que la longitud del segmento debe estar dentro de los límites especificados), entonces el algoritmo descrito probablemente se generalizará fácilmente a estos casos: de todos modos, el problema seguirá siendo hallar el mínimo en el arreglo con las restricciones adicionales especificadas.
Caso bidimensional del problema: búsqueda de submatriz máxima/mínima
El problema descrito en este artículo se generaliza de forma natural a dimensiones mayores. Por ejemplo, en un caso bidimensional, se convierte en la búsqueda de una submatriz de una matriz dada, que tenga la suma máxima de números en ella.
Usando la solución del caso unidimensional, es fácil obtener una solución en para el caso bidimensional: iteramos sobre todos los valores posibles de y , y calculamos las sumas de a en cada fila de la matriz. Ahora tenemos el problema unidimensional de hallar los índices y en este arreglo, que ya se puede resolver en tiempo lineal.
Se conocen algoritmos más rápidos para resolver este problema, pero no son mucho más rápidos que , y son muy complejos (tan complejos que muchos de ellos son inferiores al algoritmo trivial para todas las restricciones razonables por la constante oculta). Actualmente, el mejor algoritmo conocido funciona en tiempo (T. Chan 2007 “More algorithms for all-pairs shortest paths in weighted graphs”)
Este algoritmo de Chan, así como muchos otros resultados en esta área, de hecho describen multiplicación rápida de matrices (donde multiplicación de matrices significa multiplicación modificada: se usa el mínimo en lugar de la suma, y la suma en lugar de la multiplicación). El problema de hallar la submatriz con la mayor suma se puede reducir al problema de hallar los caminos más cortos entre todos los pares de vértices, y este problema, a su vez, se puede reducir a tal multiplicación de matrices.
Búsqueda de un subarreglo con promedio máximo/mínimo
Este problema consiste en hallar un segmento tal que el valor promedio sea máximo:
Por supuesto, si no se imponen otras condiciones sobre el segmento pedido , entonces la solución siempre será un segmento de longitud en el elemento máximo del arreglo. El problema solo tiene sentido si hay restricciones adicionales (por ejemplo, la longitud del segmento deseado está acotada por abajo).
En este caso, aplicamos la técnica estándar al trabajar con problemas del valor promedio: seleccionaremos el valor promedio máximo deseado mediante búsqueda binaria.
Para ello, necesitamos aprender a resolver el siguiente subproblema: se da el número , y necesitamos comprobar si existe un subarreglo del arreglo (por supuesto, que satisfaga todas las restricciones adicionales del problema), donde el valor promedio es mayor que .
Para resolver este subproblema, restamos de cada elemento del arreglo . Entonces nuestro subproblema de hecho se convierte en este: si hay o no subarreglos de suma positiva en este arreglo. Y ya sabemos cómo resolver este problema.
Así, obtuvimos la solución para la cota asintótica , donde es la precisión pedida, es el tiempo de resolver la subtarea para un arreglo de longitud (que puede variar según las restricciones adicionales concretas impuestas).
Resolver el problema online
La condición del problema es la siguiente: se da un arreglo de números, y un número . Hay consultas de la forma , y en respuesta a cada consulta, se pide hallar un subarreglo del segmento de longitud no menor que con la media aritmética máxima posible.
El algoritmo para resolver este problema es bastante complejo. KADR (Yaroslav Tverdokhleb) describió su algoritmo en el foro ruso .