Supongamos que se nos da una matriz de n enteros que representan los precios de las acciones en un solo día. Queremos encontrar un par (buyDay, sellDay) , con buyDay ≤ sellDay , de modo que si compramos las acciones en buyDay y las vendemos en sellDay , maximicemos nuestras ganancias.
Claramente, hay una solución O (n 2 ) para el algoritmo probando todos los pares posibles (buyDay, sellDay) y sacando lo mejor de todos ellos. Sin embargo, ¿hay un algoritmo mejor, tal vez uno que se ejecute en O (n) tiempo?
arrays
algorithm
big-o
time-complexity
Ajeet Ganga
fuente
fuente
Respuestas:
Amo este problema Es una pregunta clásica de la entrevista y, dependiendo de cómo lo piense, terminará obteniendo mejores y mejores soluciones. Ciertamente es posible hacer esto en un tiempo mejor que O (n 2 ), y he enumerado tres formas diferentes en las que puede pensar sobre el problema aquí. ¡Espero que esto responda tu pregunta!
Primero, la solución de divide y vencerás. Veamos si podemos resolver esto dividiendo la entrada a la mitad, resolviendo el problema en cada subconjunto y luego combinando los dos. Resulta que en realidad podemos hacer esto, ¡y podemos hacerlo de manera eficiente! La intuición es la siguiente. Si tenemos un solo día, la mejor opción es comprar ese día y luego venderlo el mismo día sin fines de lucro. De lo contrario, divida la matriz en dos mitades. Si pensamos en cuál podría ser la respuesta óptima, debe estar en uno de tres lugares:
Podemos obtener los valores para (1) y (2) invocando recursivamente nuestro algoritmo en la primera y segunda mitades. Para la opción (3), la forma de obtener el mayor beneficio sería comprar en el punto más bajo en la primera mitad y vender en el punto más alto en la segunda mitad. Podemos encontrar los valores mínimos y máximos en las dos mitades simplemente haciendo un escaneo lineal simple sobre la entrada y encontrando los dos valores. Esto nos da un algoritmo con la siguiente recurrencia:
Usando el Teorema Maestro para resolver la recurrencia, encontramos que esto se ejecuta en tiempo O (n lg n) y usará espacio O (lg n) para las llamadas recursivas. ¡Acabamos de superar la ingenua solución O (n 2 )!
¡Pero espera! Podemos hacer mucho mejor que esto. Tenga en cuenta que la única razón por la que tenemos un término O (n) en nuestra recurrencia es que tuvimos que escanear toda la entrada tratando de encontrar los valores mínimo y máximo en cada mitad. Como ya estamos explorando recursivamente cada mitad, ¡tal vez podamos hacerlo mejor si la recursión también devuelve los valores mínimos y máximos almacenados en cada mitad! En otras palabras, nuestra recursión devuelve tres cosas:
Estos dos últimos valores se pueden calcular de forma recursiva utilizando una recursión directa que podemos ejecutar al mismo tiempo que la recursividad para calcular (1):
Si usamos este enfoque, nuestra relación de recurrencia es ahora
¡Usar el Teorema maestro aquí nos da un tiempo de ejecución de O (n) con espacio O (lg n), que es incluso mejor que nuestra solución original!
Pero espere un minuto, ¡podemos hacerlo aún mejor que esto! Pensemos en resolver este problema usando programación dinámica. La idea será pensar en el problema de la siguiente manera. Supongamos que conocemos la respuesta al problema después de mirar los primeros k elementos. ¿Podríamos usar nuestro conocimiento del elemento st (k + 1), combinado con nuestra solución inicial, para resolver el problema de los primeros elementos (k + 1)? Si es así, podríamos poner en marcha un gran algoritmo resolviendo el problema para el primer elemento, luego los dos primeros, luego los primeros tres, etc. hasta que lo hayamos calculado para los primeros n elementos.
Pensemos en cómo hacer esto. Si solo tenemos un elemento, ya sabemos que tiene que ser el mejor par de compra / venta. Ahora suponga que conocemos la mejor respuesta para los primeros k elementos y observe el elemento (k + 1) st. Entonces, la única forma en que este valor puede crear una solución mejor que la que teníamos para los primeros k elementos es si la diferencia entre el más pequeño de los primeros k elementos y ese nuevo elemento es mayor que la mayor diferencia que hemos calculado hasta ahora. Supongamos que a medida que avanzamos por los elementos, hacemos un seguimiento de dos valores: el valor mínimo que hemos visto hasta ahora y el beneficio máximo que podríamos obtener con solo los primeros k elementos. Inicialmente, el valor mínimo que hemos visto hasta ahora es el primer elemento, y el beneficio máximo es cero. Cuando vemos un nuevo elemento, primero actualizamos nuestro beneficio óptimo calculando cuánto ganaríamos comprando al precio más bajo visto hasta ahora y vendiendo al precio actual. Si esto es mejor que el valor óptimo que hemos calculado hasta ahora, entonces actualizamos la solución óptima para que sea este nuevo beneficio. A continuación, actualizamos el elemento mínimo visto hasta ahora como el mínimo del elemento más pequeño actual y el nuevo elemento.
Como en cada paso solo hacemos O (1) trabajo y estamos visitando cada uno de los n elementos exactamente una vez, ¡esto toma O (n) tiempo para completar! Además, solo utiliza O (1) almacenamiento auxiliar. ¡Esto es tan bueno como hemos llegado hasta ahora!
Como ejemplo, en sus entradas, así es como podría ejecutarse este algoritmo. Los números entre cada uno de los valores de la matriz corresponden a los valores que posee el algoritmo en ese punto. En realidad, no almacenaría todo esto (¡requeriría memoria O (n)!), Pero es útil ver evolucionar el algoritmo:
Respuesta: (5, 10)
Respuesta: (4, 12)
Respuesta: (1, 5)
¿Podemos hacerlo mejor ahora? Lamentablemente, no en un sentido asintótico. Si usamos menos del tiempo O (n), no podemos ver todos los números en entradas grandes y, por lo tanto, no podemos garantizar que no perderemos la respuesta óptima (podríamos simplemente "ocultarla" en los elementos que no miró) Además, no podemos usar menos de O (1) espacio. Puede haber algunas optimizaciones a los factores constantes ocultos en la notación big-O, pero de lo contrario no podemos esperar encontrar opciones radicalmente mejores.
En general, esto significa que tenemos los siguientes algoritmos:
¡Espero que esto ayude!
EDITAR : Si está interesado, he codificado una versión de Python de estos cuatro algoritmos para que pueda jugar con ellos y juzgar sus rendimientos relativos. Aquí está el código:
fuente
Este es el problema de subsecuencia de suma máxima con un poco de indirección. Al problema de subsecuencia de suma máxima se le da una lista de enteros que podrían ser positivos o negativos, encuentre la suma más grande de un subconjunto contiguo de esa lista.
Puede convertir trivialmente este problema a ese problema tomando la ganancia o pérdida entre días consecutivos. Por lo que sería transformar una lista de precios de las acciones, por ejemplo,
[5, 6, 7, 4, 2]
en una lista de ganancias / pérdidas, por ejemplo,[1, 1, -3, -2]
. El problema de la suma de subsecuencias es bastante fácil de resolver: encuentre la subsecuencia con la mayor suma de elementos en una matrizfuente
No estoy realmente seguro de por qué esto se considera una pregunta de programación dinámica. He visto esta pregunta en libros de texto y guías de algoritmos que utilizan O (n log n) runtime y O (log n) para espacio (por ejemplo, Elementos de entrevistas de programación). Parece un problema mucho más simple de lo que la gente pretende.
Esto funciona haciendo un seguimiento de la ganancia máxima, el precio mínimo de compra y, en consecuencia, el precio óptimo de compra / venta. A medida que pasa por cada elemento de la matriz, verifica si el elemento dado es más pequeño que el precio mínimo de compra. Si es así, el índice de precio mínimo de compra, (
min
), se actualiza para ser el índice de ese elemento. Además, para cada elemento, elbecomeABillionaire
algoritmo verifica siarr[i] - arr[min]
(la diferencia entre el elemento actual y el precio mínimo de compra) es mayor que la ganancia actual. Si es así, el beneficio se actualiza a esa diferencia y la compra se establece enarr[min]
y la venta se establece enarr[i]
.Corre en una sola pasada.
Coautor: https://stackoverflow.com/users/599402/ephraim
fuente
El problema es idéntico a la subsecuencia máxima.
Lo resolví usando programación dinámica. Mantenga un registro de las actuales y anteriores (Beneficio, fecha de compra y fecha de venta) Si la corriente es más alta que la anterior, reemplace la anterior con la actual.
fuente
Aquí está mi solución Java:
fuente
Se me ocurrió una solución simple: el código se explica más por sí mismo. Es una de esas preguntas de programación dinámica.
El código no se ocupa de la comprobación de errores y los casos extremos. Es solo una muestra para dar la idea de la lógica básica para resolver el problema.
fuente
Aquí está mi solución. modifica el algoritmo de subsecuencia máxima. Resuelve el problema en O (n). Creo que no se puede hacer más rápido.
fuente
Este es un problema interesante, porque parece difícil, pero un pensamiento cuidadoso produce una solución elegante y sencilla.
Como se ha observado, se puede resolver la fuerza bruta en el tiempo O (N ^ 2). Para cada entrada en la matriz (o lista), repita todas las entradas anteriores para obtener el mínimo o el máximo dependiendo de si el problema es encontrar la mayor ganancia o pérdida.
Aquí se explica cómo pensar en una solución en O (N): cada entrada representa un nuevo máximo (o mínimo) posible. Luego, todo lo que tenemos que hacer es guardar el mínimo anterior (o máximo) y comparar el diferencial con el mínimo actual (o máximo) actual. Pan comido.
Aquí está el código, en Java como una prueba JUnit:
En el caso de calcular la mayor pérdida, hacemos un seguimiento del máximo en la lista (precio de compra) hasta la entrada actual. Luego calculamos la diferencia entre la entrada máxima y la actual. Si max - current> maxLoss, mantenemos esta diferencia como el nuevo maxLoss. Dado que se garantiza que el índice de max es menor que el índice de corriente, garantizamos que la fecha de 'compra' es menor que la fecha de 'venta'.
En el caso de calcular la mayor ganancia, todo se invierte. Llevamos un registro del mínimo en la lista hasta la entrada actual. Calculamos la diferencia entre la entrada mínima y la actual (invirtiendo el orden en la resta). Si actual - min> maxGain, entonces mantenemos esta diferencia como el nuevo maxGain. Nuevamente, el índice de la 'compra' (min) viene antes que el índice de la corriente ('venta').
Solo necesitamos hacer un seguimiento de maxGain (o maxLoss) y el índice de min o max, pero no de ambos, y no necesitamos comparar índices para validar que 'comprar' es menor que 'vender', ya que obtén esto naturalmente.
fuente
Máximo beneficio de venta única, solución O (n)
Aquí hay un proyecto que realiza pruebas de complejidad de tiempo en enfoques o (N) vs o (n ^ 2) en un conjunto de datos aleatorios en 100k ints. O (n ^ 2) toma 2 segundos, mientras que O (n) toma 0.01s
https://github.com/gulakov/complexity.js
Este es el enfoque más lento, o (n ^ 2) que recorre el resto de los días para cada día, doble bucle.
fuente
La respuesta más votada no permite los casos en que el beneficio máximo es negativo y debe modificarse para permitir tales casos. Uno puede hacerlo limitando el rango del ciclo a (len (a) - 1) y cambiando la forma en que se determina la ganancia al cambiar el índice por uno.
Compare esta versión de la función con la anterior para la matriz:
fuente
fuente
Una posibilidad para determinar el beneficio máximo podría ser realizar un seguimiento de los elementos mínimos del lado izquierdo y máximo del lado derecho en la matriz en cada índice de la matriz. Cuando esté iterando a través de los precios de las acciones, para un día determinado sabrá el precio más bajo hasta ese día, y también sabrá el precio máximo después (e incluido) de ese día.
Por ejemplo, vamos a definir una
min_arr
ymax_arr
, con ser la matriz dadaarr
. Indexi
inmin_arr
sería el elemento mínimoarr
para todos los índices<= i
(a la izquierda de e incluyendo i). Indexi
inmax_arr
sería el elemento máximoarr
para todos los índices>= i
(derecho de e incluyendo i). Luego, puede encontrar la diferencia máxima entre los elementos correspondientes enmax_arr
y `min_arr ':Esto debería ejecutarse en el tiempo O (n), pero creo que consume mucho espacio.
fuente
Esta es la máxima diferencia entre dos elementos en la matriz y esta es mi solución:
O (N) complejidad temporal O (1) complejidad espacial
fuente
Después de reprobar esto en un examen de codificación en vivo para un puesto de ingeniero de soluciones de FB, tuve que resolverlo en una atmósfera tranquila y fresca, así que aquí están mis 2 centavos:
fuente
La única respuesta que realmente responde a la pregunta es la de @akash_magoon (¡y de una manera tan simple!), Pero no devuelve el objeto exacto especificado en la pregunta. Refactoré un poco y mi respuesta en PHP devolvió justo lo que se pregunta:
fuente
Una solución ordenada:
fuente
Este programa en python3 puede devolver el precio de compra y el precio de venta que maximizarán el beneficio, calculado con la complejidad de tiempo de O (n) y la complejidad de espacio de O (1) .
fuente
Aquí está mi solución.
fuente
Para todas las respuestas que realizan un seguimiento de los elementos mínimos y máximos, esa solución es en realidad una solución O (n ^ 2). Esto se debe a que al final se debe verificar si el máximo se produjo después del mínimo o no. En caso de que no sea así, se requieren más iteraciones hasta que se cumpla esa condición, y esto deja el peor de los casos de O (n ^ 2). Y si desea omitir las iteraciones adicionales, se requiere mucho más espacio. De cualquier manera, un no-no en comparación con la solución de programación dinámica
fuente