Tengo un conjunto de enteros. Quiero encontrar la subsecuencia cada vez mayor de ese conjunto utilizando la programación dinámica.
215
Tengo un conjunto de enteros. Quiero encontrar la subsecuencia cada vez mayor de ese conjunto utilizando la programación dinámica.
Respuestas:
OK, describiré primero la solución más simple que es O (N ^ 2), donde N es el tamaño de la colección. También existe una solución O (N log N), que también describiré. Busque aquí en la sección Algoritmos eficientes.
Asumiré que los índices de la matriz son de 0 a N - 1. Así que definamos
DP[i]
la longitud del LIS (subsecuencia creciente más larga) que termina en el elemento con índicei
. Para calcularDP[i]
observamos todos los índicesj < i
y verificamos siDP[j] + 1 > DP[i]
yarray[j] < array[i]
(queremos que aumente). Si esto es cierto, podemos actualizar el óptimo actual paraDP[i]
. Para encontrar el óptimo global para la matriz, puede tomar el valor máximo deDP[0...N - 1]
.Utilizo la matriz
prev
para poder encontrar más tarde la secuencia real no solo su longitud. Simplemente regrese recursivamente desdebestEnd
un bucle usandoprev[bestEnd]
. El-1
valor es una señal para detenerse.OK, ahora a la
O(N log N)
solución más eficiente :Dejado
S[pos]
ser definido como el número entero más pequeño que termina una secuencia creciente de longitudpos
. Ahora recorra cada número enteroX
del conjunto de entrada y haga lo siguiente:Si
X
> último elemento enS
, anexarX
al final deS
. Esto significa esencialmente que hemos encontrado una nueva más grandeLIS
.De lo contrario, encuentre el elemento más pequeño en
S
, que es>=
queX
, y cámbielo aX
. Debido a queS
se ordena en cualquier momento, el elemento se puede encontrar mediante la búsqueda binaria enlog(N)
.Tiempo de ejecución total:
N
enteros y una búsqueda binaria para cada uno de ellos: N * log (N) = O (N log N)Ahora hagamos un ejemplo real:
Colección de enteros:
2 6 3 4 1 2 9 5 8
Pasos:
Entonces, la longitud del LIS es
5
(el tamaño de S).Para reconstruir lo real
LIS
usaremos nuevamente una matriz principal. Dejeparent[i]
ser el predecesor del elemento con índicei
en laLIS
terminación en el elemento con índicei
.Para simplificar las cosas, podemos mantener en la matriz
S
, no los enteros reales, sino sus índices (posiciones) en el conjunto. No guardamos{1, 2, 4, 5, 8}
, pero guardamos{4, 5, 3, 7, 8}
.Es decir, entrada [4] = 1 , entrada [5] = 2 , entrada [3] = 4 , entrada [7] = 5 , entrada [8] = 8 .
Si actualizamos correctamente la matriz principal, el LIS real es:
Ahora a lo importante: ¿cómo actualizamos la matriz principal? Hay dos opciones:
Si
X
> último elemento enS
, entoncesparent[indexX] = indexLastElement
. Esto significa que el padre del elemento más nuevo es el último elemento. Simplemente anteponemosX
al final deS
.De lo contrario, encuentre el índice del elemento más pequeño en
S
, que es>=
queX
, y cámbielo aX
. Aquíparent[indexX] = S[index - 1]
.fuente
DP[j] + 1 == DP[i]
entoncesDP[i]
no va a mejorar conDP[i] = DP[j] + 1
. Estamos intentando optimizarDP[i]
.[1,2,5,8]
, 4 viene antes que 1 en la matriz, ¿cómo puede ser el LIS[1,2,4,5,8]
?[2,3,4,5,8]
. Lea atentamente: laS
matrizDOES NOT
representa una secuencia real.Let S[pos] be defined as the smallest integer that ends an increasing sequence of length pos.
La explicación de Petar Minchev me ayudó a aclarar las cosas, pero me resultó difícil analizar qué era todo, así que hice una implementación de Python con nombres de variables excesivamente descriptivos y muchos comentarios. Hice una solución recursiva ingenua, la solución O (n ^ 2) y la solución O (n log n).
¡Espero que ayude a aclarar los algoritmos!
La solución recursiva
La solución de programación dinámica O (n ^ 2)
La solución de programación dinámica O (n log n)
fuente
bisect
. Para una demostración de cómo funciona un algoritmo y sus características de rendimiento, estaba tratando de mantener las cosas lo más primitivas posible.Hablando de la solución DP, me pareció sorprendente que nadie mencionara el hecho de que LIS puede reducirse a LCS . Todo lo que necesita hacer es ordenar la copia de la secuencia original, eliminar todos los duplicados y hacer LCS de ellos. En pseudocódigo es:
Y la implementación completa escrita en Go. No necesita mantener toda la matriz n ^ 2 DP si no necesita reconstruir la solución.
fuente
La siguiente implementación de C ++ incluye también algo de código que construye la subsecuencia creciente más larga real usando una matriz llamada
prev
.La implementación sin pila simplemente invierte el vector
fuente
Aquí hay tres pasos para evaluar el problema desde el punto de vista de la programación dinámica:
Si tomamos como ejemplo la secuencia {0, 8, 2, 3, 7, 9}, en el índice:
Aquí está el código de trabajo de C ++ 11:
fuente
Aquí hay una implementación Scala del algoritmo O (n ^ 2):
fuente
Aquí hay otra implementación O (n ^ 2) JAVA. Sin recursión / memorización para generar la subsecuencia real. Solo una matriz de cadenas que almacena el LIS real en cada etapa y una matriz para almacenar la longitud del LIS para cada elemento. Bastante fácil. Echar un vistazo:
Código en acción: http://ideone.com/sBiOQx
fuente
Esto se puede resolver en O (n ^ 2) utilizando la programación dinámica. El código de Python para el mismo sería como: -
Para entrada:
5 19 5 81 50 28 29 1 83 23
la salida sería:
[1, 2, 1, 3, 3, 3, 4, 1, 5, 3] 5
El list_index de output list es list_index de input list. El valor en un índice_de_lista dado en la lista de salida denota la mayor longitud de subsecuencia creciente para ese índice_de_lista.
fuente
aquí está la implementación de Java O (nlogn)
fuente
Esta es una implementación de Java en O (n ^ 2). Simplemente no utilicé la Búsqueda binaria para encontrar el elemento más pequeño en S, que es> = que X. Solo utilicé un bucle for. El uso de la búsqueda binaria haría que la complejidad en O (n logn)
fuente
verifique el código en Java para la subsecuencia creciente más larga con los elementos de la matriz
http://ideone.com/Nd2eba
fuente
Esto se puede resolver en O (n ^ 2) usando programación dinámica.
Procese los elementos de entrada en orden y mantenga una lista de tuplas para cada elemento. Cada tupla (A, B), para el elemento i denotará, A = longitud de la subsecuencia creciente más larga que termina en i y B = índice del predecesor de la lista [i] en la subsecuencia creciente más larga que termina en la lista [i ]
Comience desde el elemento 1, la lista de tuplas para el elemento 1 será [(1,0)] para el elemento i, escanee la lista 0..i y encuentre la lista de elementos [k] tal que la lista [k] <lista [i] , el valor de A para el elemento i, Ai será Ak + 1 y Bi será k. Si hay varios de estos elementos, agréguelos a la lista de tuplas para el elemento i.
Al final, encuentre todos los elementos con el valor máximo de A (longitud de LIS que termina en el elemento) y retroceda utilizando las tuplas para obtener la lista.
He compartido el código para el mismo en http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799
fuente
O (n ^ 2) implementación de Java:
fuente
a pesar de que hay una manera de resolver esto en tiempo O (nlogn) (esto resuelve en tiempo O (n ^ 2)) pero de esta manera se obtiene el enfoque de programación dinámica que también es bueno.
fuente
Aquí está mi solución de Leetcode usando Búsqueda binaria: ->
fuente
La solución LIS más simple en C ++ con O (nlog (n)) complejidad de tiempo
SALIDA:
4
fuente
Subsecuencia creciente más larga (Java)
fuente
He implementado LIS en Java usando programación dinámica y memorización. Junto con el código, he realizado cálculos de complejidad, es decir, por qué es O (n Log (base2) n). Como siento, las explicaciones teóricas o lógicas son buenas, pero la demostración práctica siempre es mejor para comprender.
Mientras ejecutaba el código anterior,
fuente