Máximo beneficio de venta única

123

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?

Ajeet Ganga
fuente
2
Este es el problema de subsecuencia de suma máxima con un nivel de indirección.
MSN
2
@MSN: ¿Cómo es eso? Él no mira las sumas en absoluto, sino más bien las diferencias entre los elementos.
PengOne
@ PengOne- Es cierto, pero esa pregunta estaba cerrada. Reformulé la pregunta para que fuera más fácil de entender, ¿podríamos tratar de mantenerla abierta?
templatetypedef
2
@PengOne, como dije, tiene un nivel de indirección. Específicamente, desea maximizar la suma de ganancias / pérdidas en un conjunto de días contiguos. Por lo tanto, convierta la lista en ganancias / pérdidas y encuentre la suma máxima de subsecuencia.
MSN
1
@PDN: Eso no funcionará porque min puede ocurrir antes de max. No puede vender acciones (en este caso) y comprarlas más tarde.
Ajeet Ganga

Respuestas:

287

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:

  1. El par de compra / venta correcto ocurre completamente dentro de la primera mitad.
  2. El par de compra / venta correcto ocurre completamente dentro de la segunda mitad.
  3. El par de compra / venta correcto ocurre en ambas mitades: compramos en la primera mitad y luego vendemos en la segunda mitad.

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:

T(1) <= O(1)
T(n) <= 2T(n / 2) + O(n)

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:

  1. Los tiempos de compra y venta para maximizar las ganancias.
  2. El valor mínimo general en el rango.
  3. El valor máximo global en el rango.

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):

  1. Los valores máximo y mínimo de un rango de un solo elemento son solo ese elemento.
  2. Los valores máximo y mínimo de un rango de elementos múltiples se pueden encontrar dividiendo la entrada a la mitad, encontrando los valores máximo y mínimo de cada mitad, luego tomando sus respectivos valores máximo y mínimo.

Si usamos este enfoque, nuestra relación de recurrencia es ahora

T(1) <= O(1)
T(n) <= 2T(n / 2) + O(1)

¡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:

            5        10        4          6         7
min         5         5        4          4         4    
best      (5,5)     (5,10)   (5,10)     (5,10)    (5,10)

Respuesta: (5, 10)

            5        10        4          6        12
min         5         5        4          4         4    
best      (5,5)     (5,10)   (5,10)     (5,10)    (4,12)

Respuesta: (4, 12)

            1       2       3      4      5
min         1       1       1      1      1
best      (1,1)   (1,2)   (1,3)  (1,4)  (1,5)

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:

  • Ingenuo: O (n 2 ) tiempo, O (1) espacio.
  • Divide y vencerás: O (n lg n) tiempo, O (lg n) espacio.
  • Divide y vencerás optimizado: O (n) tiempo, O (lg n) espacio.
  • Programación dinámica: O (n) tiempo, O (1) espacio.

¡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:

# Four different algorithms for solving the maximum single-sell profit problem,
# each of which have different time and space complexity.  This is one of my
# all-time favorite algorithms questions, since there are so many different
# answers that you can arrive at by thinking about the problem in slightly
# different ways.
#
# The maximum single-sell profit problem is defined as follows.  You are given
# an array of stock prices representing the value of some stock over time.
# Assuming that you are allowed to buy the stock exactly once and sell the
# stock exactly once, what is the maximum profit you can make?  For example,
# given the prices
#
#                        2, 7, 1, 8, 2, 8, 4, 5, 9, 0, 4, 5
#
# The maximum profit you can make is 8, by buying when the stock price is 1 and
# selling when the stock price is 9.  Note that while the greatest difference
# in the array is 9 (by subtracting 9 - 0), we cannot actually make a profit of
# 9 here because the stock price of 0 comes after the stock price of 9 (though
# if we wanted to lose a lot of money, buying high and selling low would be a
# great idea!)
#
# In the event that there's no profit to be made at all, we can always buy and
# sell on the same date.  For example, given these prices (which might
# represent a buggy-whip manufacturer:)
#
#                            9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#
# The best profit we can make is 0 by buying and selling on the same day.
#
# Let's begin by writing the simplest and easiest algorithm we know of that
# can solve this problem - brute force.  We will just consider all O(n^2) pairs
# of values, and then pick the one with the highest net profit.  There are
# exactly n + (n - 1) + (n - 2) + ... + 1 = n(n + 1)/2 different pairs to pick
# from, so this algorithm will grow quadratically in the worst-case.  However,
# it uses only O(1) memory, which is a somewhat attractive feature.  Plus, if
# our first intuition for the problem gives a quadratic solution, we can be
# satisfied that if we don't come up with anything else, we can always have a
# polynomial-time solution.

def BruteForceSingleSellProfit(arr):
    # Store the best possible profit we can make; initially this is 0.
    bestProfit = 0;

    # Iterate across all pairs and find the best out of all of them.  As a
    # minor optimization, we don't consider any pair consisting of a single
    # element twice, since we already know that we get profit 0 from this.
    for i in range(0, len(arr)):
        for j in range (i + 1, len(arr)):
            bestProfit = max(bestProfit, arr[j] - arr[i])

    return bestProfit

# This solution is extremely inelegant, and it seems like there just *has* to
# be a better solution.  In fact, there are many better solutions, and we'll
# see three of them.
#
# The first insight comes if we try to solve this problem by using a divide-
# and-conquer strategy.  Let's consider what happens if we split the array into
# two (roughly equal) halves.  If we do so, then there are three possible
# options about where the best buy and sell times are:
#
# 1. We should buy and sell purely in the left half of the array.
# 2. We should buy and sell purely in the right half of the array.
# 3. We should buy in the left half of the array and sell in the right half of
#    the array.
#
# (Note that we don't need to consider selling in the left half of the array
# and buying in the right half of the array, since the buy time must always
# come before the sell time)
#
# If we want to solve this problem recursively, then we can get values for (1)
# and (2) by recursively invoking the algorithm on the left and right
# subarrays.  But what about (3)?  Well, if we want to maximize our profit, we
# should be buying at the lowest possible cost in the left half of the array
# and selling at the highest possible cost in the right half of the array.
# This gives a very elegant algorithm for solving this problem:
#
#    If the array has size 0 or size 1, the maximum profit is 0.
#    Otherwise:
#       Split the array in half.
#       Compute the maximum single-sell profit in the left array, call it L.
#       Compute the maximum single-sell profit in the right array, call it R.
#       Find the minimum of the first half of the array, call it Min
#       Find the maximum of the second half of the array, call it Max
#       Return the maximum of L, R, and Max - Min.
#
# Let's consider the time and space complexity of this algorithm.  Our base
# case takes O(1) time, and in our recursive step we make two recursive calls,
# one on each half of the array, and then does O(n) work to scan the array
# elements to find the minimum and maximum values.  This gives the recurrence
#
#    T(1)     = O(1)
#    T(n / 2) = 2T(n / 2) + O(n)
#
# Using the Master Theorem, this recurrence solves to O(n log n), which is
# asymptotically faster than our original approach!  However, we do pay a
# (slight) cost in memory usage.  Because we need to maintain space for all of
# the stack frames we use.  Since on each recursive call we cut the array size
# in half, the maximum number of recursive calls we can make is O(log n), so
# this algorithm uses O(n log n) time and O(log n) memory.

def DivideAndConquerSingleSellProfit(arr):
    # Base case: If the array has zero or one elements in it, the maximum
    # profit is 0.
    if len(arr) <= 1:
        return 0;

    # Cut the array into two roughly equal pieces.
    left  = arr[ : len(arr) / 2]
    right = arr[len(arr) / 2 : ]

    # Find the values for buying and selling purely in the left or purely in
    # the right.
    leftBest  = DivideAndConquerSingleSellProfit(left)
    rightBest = DivideAndConquerSingleSellProfit(right)

    # Compute the best profit for buying in the left and selling in the right.
    crossBest = max(right) - min(left)

    # Return the best of the three
    return max(leftBest, rightBest, crossBest)

# While the above algorithm for computing the maximum single-sell profit is
# better timewise than what we started with (O(n log n) versus O(n^2)), we can
# still improve the time performance.  In particular, recall our recurrence
# relation:
#
#    T(1) = O(1)
#    T(n) = 2T(n / 2) + O(n)
#
# Here, the O(n) term in the T(n) case comes from the work being done to find
# the maximum and minimum values in the right and left halves of the array,
# respectively.  If we could find these values faster than what we're doing
# right now, we could potentially decrease the function's runtime.
#
# The key observation here is that we can compute the minimum and maximum
# values of an array using a divide-and-conquer approach.  Specifically:
#
#    If the array has just one element, it is the minimum and maximum value.
#    Otherwise:
#       Split the array in half.
#       Find the minimum and maximum values from the left and right halves.
#       Return the minimum and maximum of these two values.
#
# Notice that our base case does only O(1) work, and our recursive case manages
# to do only O(1) work in addition to the recursive calls.  This gives us the
# recurrence relation
#
#    T(1) = O(1)
#    T(n) = 2T(n / 2) + O(1)
#
# Using the Master Theorem, this solves to O(n).
#
# How can we make use of this result?  Well, in our current divide-and-conquer
# solution, we split the array in half anyway to find the maximum profit we
# could make in the left and right subarrays.  Could we have those recursive
# calls also hand back the maximum and minimum values of the respective arrays?
# If so, we could rewrite our solution as follows:
#
#    If the array has size 1, the maximum profit is zero and the maximum and
#       minimum values are the single array element.
#    Otherwise:
#       Split the array in half.
#       Compute the maximum single-sell profit in the left array, call it L.
#       Compute the maximum single-sell profit in the right array, call it R.
#       Let Min be the minimum value in the left array, which we got from our
#           first recursive call.
#       Let Max be the maximum value in the right array, which we got from our
#           second recursive call.
#       Return the maximum of L, R, and Max - Min for the maximum single-sell
#           profit, and the appropriate maximum and minimum values found from
#           the recursive calls.
#
# The correctness proof for this algorithm works just as it did before, but now
# we never actually do a scan of the array at each step.  In fact, we do only
# O(1) work at each level.  This gives a new recurrence
#
#     T(1) = O(1)
#     T(n) = 2T(n / 2) + O(1)
#
# Which solves to O(n).  We're now using O(n) time and O(log n) memory, which
# is asymptotically faster than before!
#
# The code for this is given below:

def OptimizedDivideAndConquerSingleSellProfit(arr):
    # If the array is empty, the maximum profit is zero.
    if len(arr) == 0:
        return 0

    # This recursive helper function implements the above recurrence.  It
    # returns a triple of (max profit, min array value, max array value).  For
    # efficiency reasons, we always reuse the array and specify the bounds as
    # [lhs, rhs]
    def Recursion(arr, lhs, rhs):
        # If the array has just one element, we return that the profit is zero
        # but the minimum and maximum values are just that array value.
        if lhs == rhs:
            return (0, arr[lhs], arr[rhs])

        # Recursively compute the values for the first and latter half of the
        # array.  To do this, we need to split the array in half.  The line
        # below accomplishes this in a way that, if ported to other languages,
        # cannot result in an integer overflow.
        mid = lhs + (rhs - lhs) / 2

        # Perform the recursion.
        ( leftProfit,  leftMin,  leftMax) = Recursion(arr, lhs, mid)
        (rightProfit, rightMin, rightMax) = Recursion(arr, mid + 1, rhs)

        # Our result is the maximum possible profit, the minimum of the two
        # minima we've found (since the minimum of these two values gives the
        # minimum of the overall array), and the maximum of the two maxima.
        maxProfit = max(leftProfit, rightProfit, rightMax - leftMin)
        return (maxProfit, min(leftMin, rightMin), max(leftMax, rightMax))

    # Using our recursive helper function, compute the resulting value.
    profit, _, _ = Recursion(arr, 0, len(arr) - 1)
    return profit

# At this point we've traded our O(n^2)-time, O(1)-space solution for an O(n)-
# time, O(log n) space solution.  But can we do better than this?
#
# To find a better algorithm, we'll need to switch our line of reasoning.
# Rather than using divide-and-conquer, let's see what happens if we use
# dynamic programming.  In particular, let's think about the following problem.
# If we knew the maximum single-sell profit that we could get in just the first
# k array elements, could we use this information to determine what the
# maximum single-sell profit would be in the first k + 1 array elements?  If we
# could do this, we could use the following algorithm:
#
#   Find the maximum single-sell profit to be made in the first 1 elements.
#   For i = 2 to n:
#      Compute the maximum single-sell profit using the first i elements.
#
# How might we do this?  One intuition is as follows.  Suppose that we know the
# maximum single-sell profit of the first k elements.  If we look at k + 1
# elements, then either the maximum profit we could make by buying and selling
# within the first k elements (in which case nothing changes), or we're
# supposed to sell at the (k + 1)st price.  If we wanted to sell at this price
# for a maximum profit, then we would want to do so by buying at the lowest of
# the first k + 1 prices, then selling at the (k + 1)st price.
#
# To accomplish this, suppose that we keep track of the minimum value in the
# first k elements, along with the maximum profit we could make in the first
# k elements.  Upon seeing the (k + 1)st element, we update what the current
# minimum value is, then update what the maximum profit we can make is by
# seeing whether the difference between the (k + 1)st element and the new
# minimum value is.  Note that it doesn't matter what order we do this in; if
# the (k + 1)st element is the smallest element so far, there's no possible way
# that we could increase our profit by selling at that point.
#
# To finish up this algorithm, we should note that given just the first price,
# the maximum possible profit is 0.
#
# This gives the following simple and elegant algorithm for the maximum single-
# sell profit problem:
#
#   Let profit = 0.
#   Let min = arr[0]
#   For k = 1 to length(arr):
#       If arr[k] < min, set min = arr[k]
#       If profit < arr[k] - min, set profit = arr[k] - min
#
# This is short, sweet, and uses only O(n) time and O(1) memory.  The beauty of
# this solution is that we are quite naturally led there by thinking about how
# to update our answer to the problem in response to seeing some new element.
# In fact, we could consider implementing this algorithm as a streaming
# algorithm, where at each point in time we maintain the maximum possible
# profit and then update our answer every time new data becomes available.
#
# The final version of this algorithm is shown here:

def DynamicProgrammingSingleSellProfit(arr):
    # If the array is empty, we cannot make a profit.
    if len(arr) == 0:
        return 0

    # Otherwise, keep track of the best possible profit and the lowest value
    # seen so far.
    profit = 0
    cheapest = arr[0]

    # Iterate across the array, updating our answer as we go according to the
    # above pseudocode.
    for i in range(1, len(arr)):
        # Update the minimum value to be the lower of the existing minimum and
        # the new minimum.
        cheapest = min(cheapest, arr[i])

        # Update the maximum profit to be the larger of the old profit and the
        # profit made by buying at the lowest value and selling at the current
        # price.
        profit = max(profit, arr[i] - cheapest)

    return profit

# To summarize our algorithms, we have seen
#
# Naive:                        O(n ^ 2)   time, O(1)     space
# Divide-and-conquer:           O(n log n) time, O(log n) space
# Optimized divide-and-conquer: O(n)       time, O(log n) space
# Dynamic programming:          O(n)       time, O(1)     space
templatetypedef
fuente
1
@ FrankQ.- Se necesita espacio para las dos llamadas recursivas, pero normalmente esas llamadas se realizan una tras otra. Esto significa que el compilador puede reutilizar la memoria entre llamadas; una vez que regresa una llamada, la siguiente llamada puede reutilizar su espacio. Como resultado, solo necesita memoria para retener una llamada de función a la vez, por lo que el uso de la memoria es proporcional a la profundidad máxima de la pila de llamadas. Como la recursión termina en los niveles de O (log n), solo se necesita usar la memoria de O (log n). ¿Eso aclara las cosas?
templatetypedef
¿Alguien podría portar estos a Ruby? Parte de la recursión no funciona de la misma manera que en Python. Además, estas soluciones solo devuelven el beneficio máximo; no devuelven los puntos de la matriz que arrojaron las ganancias (que podrían usarse para informar el porcentaje de aumento de ganancias en el pasado)
rcd
El concepto de programación dinámica no es realmente necesario para explicar la solución de tiempo O (n), pero es genial que vincules todos estos tipos de algoritmos.
Rn222
¿Cómo puede construir sobre cualquiera de los algoritmos sub O (n ^ 2) para encontrar todos los pares ordenados por ganancia?
ferk86
@templatetypedef ¿cómo cambiaríamos el enfoque de programación dinámica si comenzáramos con un presupuesto de M $ y en lugar de un solo stock tuviéramos m stock con precios durante n días como se indica? es decir, variamos el número de unidades de acciones compradas y los datos de existencias disponibles de 1 a n existencias (como anteriormente, solo teníamos para Google, ahora también tenemos para otras 5 empresas)
Ronak Agrawal
32

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 matriz

MSN
fuente
1
Yo no creo que sea bastante funciona de esta manera, ya que si usted compra la acción en un día inicial no lo hace se acumulan los beneficios del delta del día anterior. ¿O no es esto un problema en este enfoque?
templatetypedef
1
@templatetypedef, es por eso que realiza un seguimiento de la suma más grande y la suma de la secuencia actual. Cuando la suma de la secuencia actual va por debajo de cero, sabe que no habrá ganado dinero con esa secuencia y puede comenzar de nuevo. Al rastrear la suma más grande, encontrará automáticamente las mejores fechas de compra / venta.
MSN
66
@templatetypedef, por cierto, haces lo mismo en tu respuesta.
MSN
16

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, el becomeABillionairealgoritmo verifica si arr[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 en arr[min]y la venta se establece en arr[i].

Corre en una sola pasada.

static void becomeABillionaire(int arr[]) {
    int i = 0, buy = 0, sell = 0, min = 0, profit = 0;

    for (i = 0; i < arr.length; i++) {
        if (arr[i] < arr[min])
            min = i;
        else if (arr[i] - arr[min] > profit) {
            buy = min; 
            sell = i;
            profit = arr[i] - arr[min];
        }

    }

    System.out.println("We will buy at : " + arr[buy] + " sell at " + arr[sell] + 
            " and become billionaires worth " + profit );

}

Coautor: https://stackoverflow.com/users/599402/ephraim

Akash Magoon
fuente
2

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.

    int prices[] = { 38, 37, 35, 31, 20, 24, 35, 21, 24, 21, 23, 20, 23, 25, 27 };

    int buyDate = 0, tempbuyDate = 0;
    int sellDate = 0, tempsellDate = 0; 

    int profit = 0, tempProfit =0;
    int i ,x = prices.length;
    int previousDayPrice = prices[0], currentDayprice=0;

    for(i=1 ; i<x; i++ ) {

        currentDayprice = prices[i];

        if(currentDayprice > previousDayPrice ) {  // price went up

            tempProfit = tempProfit + currentDayprice - previousDayPrice;
            tempsellDate = i;
        }
        else { // price went down 

            if(tempProfit>profit) { // check if the current Profit is higher than previous profit....

                profit = tempProfit;
                sellDate = tempsellDate;
                buyDate = tempbuyDate;
            } 
                                     // re-intialized buy&sell date, profit....
                tempsellDate = i;
                tempbuyDate = i;
                tempProfit =0;
        }
        previousDayPrice = currentDayprice;
    }

    // if the profit is highest till the last date....
    if(tempProfit>profit) {
        System.out.println("buydate " + tempbuyDate + " selldate " + tempsellDate + " profit " + tempProfit );
    }
    else {
        System.out.println("buydate " + buyDate + " selldate " + sellDate + " profit " + profit );
    }   
Vikas
fuente
2

Aquí está mi solución Java:

public static void main(String[] args) {
    int A[] = {5,10,4,6,12};

    int min = A[0]; // Lets assume first element is minimum
    int maxProfit = 0; // 0 profit, if we buy & sell on same day.
    int profit = 0;
    int minIndex = 0; // Index of buy date
    int maxIndex = 0; // Index of sell date

    //Run the loop from next element
    for (int i = 1; i < A.length; i++) {
        //Keep track of minimum buy price & index
        if (A[i] < min) {
            min = A[i];
            minIndex = i;
        }
        profit = A[i] - min;
        //If new profit is more than previous profit, keep it and update the max index
        if (profit > maxProfit) {
            maxProfit = profit;
            maxIndex = i;
        }
    }
    System.out.println("maxProfit is "+maxProfit);
    System.out.println("minIndex is "+minIndex);
    System.out.println("maxIndex is "+maxIndex);     
}
Rohit Mendiratta
fuente
@Nitiraj, sí, esta solución es correcta, pero le pediría que lea la respuesta proporcionada por templatetypedef, ya que en la respuesta proporcionada por templatetypedef, se mencionan todas las posibles soluciones, incluida la publicada por Rohit. La solución de Rohit es en realidad una implementación de la última solución con O (n) utilizando la programación dinámica mencionada en la respuesta proporcionada por templatetypedef.
nits.kk
1
Suponga que su matriz es int A [] = {5, 4, 6, 7, 6, 3, 2, 5}; Luego, según su lógica, comprará en el índice 6 y luego lo venderá en el índice 3. Lo cual está mal. No puedes vender en el pasado. El índice de venta tiene que ser mayor que el índice de compra.
desarrollador747
1
La solución anterior es "casi" correcta. pero imprime el índice mínimo absoluto en lugar del índice del precio de "compra". Para corregir, necesita otra variable, diga minBuyIndex, que actualiza solo dentro del bloque "if (profit> maxProfit)" e imprímalo.
javabrew
1

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.

namespace MaxProfitForSharePrice
{
    class MaxProfitForSharePrice
    {
        private static int findMax(int a, int b)
        {
            return a > b ? a : b;
        }

        private static void GetMaxProfit(int[] sharePrices)
        {
            int minSharePrice = sharePrices[0], maxSharePrice = 0, MaxProft = 0;
            int shareBuyValue = sharePrices[0], shareSellValue = sharePrices[0];

            for (int i = 0; i < sharePrices.Length; i++)
            {
                if (sharePrices[i] < minSharePrice )
                {
                    minSharePrice = sharePrices[i];
                    // if we update the min value of share, we need to reset the Max value as 
                    // we can only do this transaction in-sequence. We need to buy first and then only we can sell.
                    maxSharePrice = 0; 
                }
                else 
                {
                    maxSharePrice = sharePrices[i];
                }

                // We are checking if max and min share value of stock are going to
                // give us better profit compare to the previously stored one, then store those share values.
                if (MaxProft < (maxSharePrice - minSharePrice))
                {
                    shareBuyValue = minSharePrice;
                    shareSellValue = maxSharePrice;
                }

                MaxProft = findMax(MaxProft, maxSharePrice - minSharePrice);
            }

            Console.WriteLine("Buy stock at ${0} and sell at ${1}, maximum profit can be earned ${2}.", shareBuyValue, shareSellValue, MaxProft);
        }

        static void Main(string[] args)
        {
           int[] sampleArray = new int[] { 1, 3, 4, 1, 1, 2, 11 };
           GetMaxProfit(sampleArray);
            Console.ReadLine();
        }
    }
}
vran freelance
fuente
1
public static double maxProfit(double [] stockPrices)
    {
        double initIndex = 0, finalIndex = 0;

        double tempProfit = list[1] - list[0];
        double maxSum = tempProfit;
        double maxEndPoint = tempProfit;


        for(int i = 1 ;i<list.length;i++)
        {
            tempProfit = list[ i ] - list[i - 1];;

            if(maxEndPoint < 0)
            {
                maxEndPoint = tempProfit;
                initIndex = i;
            }
            else
            {
                maxEndPoint += tempProfit;
            }

            if(maxSum <= maxEndPoint)
            {
                maxSum = maxEndPoint ;
                finalIndex = i;
            }
        }
        System.out.println(initIndex + " " + finalIndex);
        return maxSum;

    }

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.

Afaque
fuente
1

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:

import org.junit.Test;

public class MaxDiffOverSeriesProblem {

    @Test
    public void test1() {
        int[] testArr = new int[]{100, 80, 70, 65, 95, 120, 150, 75, 95, 100, 110, 120, 90, 80, 85, 90};

        System.out.println("maxLoss: " + calculateMaxLossOverSeries(testArr) + ", maxGain: " + calculateMaxGainOverSeries(testArr));
    }

    private int calculateMaxLossOverSeries(int[] arr) {
        int maxLoss = 0;

        int idxMax = 0;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > arr[idxMax]) {
                idxMax = i;
            }

            if (arr[idxMax] - arr[i] > maxLoss) {
                maxLoss = arr[idxMax] - arr[i];
            }           
        }

        return maxLoss;
    }

    private int calculateMaxGainOverSeries(int[] arr) {
        int maxGain = 0;

        int idxMin = 0;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < arr[idxMin]) {
                idxMin = i;
            }

            if (arr[i] - arr[idxMin] > maxGain) {
                maxGain = arr[i] - arr[idxMin];
            }           
        }

        return maxGain;
    }

}

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.

Steven Solomon
fuente
1

Máximo beneficio de venta única, solución O (n)

function stocks_n(price_list){
    var maxDif=0, min=price_list[0]

    for (var i in price_list){
        p = price_list[i];
        if (p<min)
            min=p
        else if (p-min>maxDif)
                maxDif=p-min;
   }

    return maxDif
}

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

function stocks_n2(ps){
    for (maxDif=0,i=_i=0;p=ps[i++];i=_i++)
        for (;p2=ps[i++];)
            if (p2-p>maxDif)
                maxDif=p2-p
    return maxDif
}

Este es el enfoque más lento, o (n ^ 2) que recorre el resto de los días para cada día, doble bucle.

Alex G
fuente
1

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.

def singSellProfit(a):
profit = -max(a)
low = a[0]

for i in range(len(a) - 1):
    low = min(low, a[i])
    profit = max(profit, a[i + 1] - low)
return profit

Compare esta versión de la función con la anterior para la matriz:

s = [19,11,10,8,5,2]

singSellProfit(s)
-1

DynamicProgrammingSingleSellProfit(s)
0
Danny Bubb
fuente
0
static void findmaxprofit(int[] stockvalues){
    int buy=0,sell=0,buyingpoint=0,sellingpoint=0,profit=0,currentprofit=0;
    int finalbuy=0,finalsell=0;
    if(stockvalues.length!=0){
        buy=stockvalues[0];
    }           
    for(int i=1;i<stockvalues.length;i++){  
        if(stockvalues[i]<buy&&i!=stockvalues.length-1){                
            buy=stockvalues[i];
            buyingpoint=i;
        }               
        else if(stockvalues[i]>buy){                
            sell=stockvalues[i];
            sellingpoint=i;
        }
        currentprofit=sell-buy;         
        if(profit<currentprofit&&sellingpoint>buyingpoint){             
            finalbuy=buy;
            finalsell=sell;
            profit=currentprofit;
        }

    }
    if(profit>0)
    System.out.println("Buy shares at "+finalbuy+" INR and Sell Shares "+finalsell+" INR and Profit of "+profit+" INR");
    else
        System.out.println("Don't do Share transacations today");
}
Mohan Raj
fuente
0

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_arry max_arr, con ser la matriz dada arr. Index iin min_arrsería el elemento mínimo arrpara todos los índices <= i(a la izquierda de e incluyendo i). Index iin max_arrsería el elemento máximo arrpara todos los índices >= i(derecho de e incluyendo i). Luego, puede encontrar la diferencia máxima entre los elementos correspondientes en max_arry `min_arr ':

def max_profit(arr)
   min_arr = []
   min_el = arr.first
   arr.each do |el|
       if el < min_el
           min_el = el
           min_arr << min_el
       else
           min_arr << min_el
       end
   end

   max_arr = []
   max_el = arr.last
   arr.reverse.each do |el|
       if el > max_el
           max_el = el
           max_arr.unshift(max_el)
       else
           max_arr.unshift(max_el)
       end

   end

   max_difference = max_arr.first - min_arr.first
   1.upto(arr.length-1) do |i|
        max_difference = max_arr[i] - min_arr[i] if max_difference < max_arr[i] - min_arr[i]  
   end

   return max_difference 
end

Esto debería ejecutarse en el tiempo O (n), pero creo que consume mucho espacio.

fibono
fuente
0

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

    int[] arr   =   {5, 4, 6 ,7 ,6 ,3 ,2, 5};

    int start   =   0;
    int end     =   0;
    int max     =   0;
    for(int i=1; i<arr.length; i++){
        int currMax =   arr[i] - arr[i-1];
        if(currMax>0){
            if((arr[i] -arr[start])>=currMax && ((arr[i] -arr[start])>=(arr[end] -arr[start]))){

                 end    =   i;
            }
            else if(currMax>(arr[i] -arr[start]) && currMax >(arr[end] - arr[start])){
                start   =   i-1;
                end =   i;
            }
        }
    }
    max =   arr[end] - arr[start];
    System.out.println("max: "+max+" start: "+start+" end: "+end);
Ahmed Mansy
fuente
0

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:

var max_profit = 0;
var stockPrices = [23,40,21,67,1,50,22,38,2,62];

var currentBestBuy = 0; 
var currentBestSell = 0;
var min = 0;

for(var i = 0;i < (stockPrices.length - 1) ; i++){
    if(( stockPrices[i + 1] - stockPrices[currentBestBuy] > max_profit) ){
        max_profit = stockPrices[i + 1] - stockPrices[currentBestBuy];
        currentBestSell = i + 1;  
    }
    if(stockPrices[i] < stockPrices[currentBestBuy]){
            min = i;
        }
    if( max_profit < stockPrices[i + 1] - stockPrices[min] ){
        max_profit = stockPrices[i + 1] - stockPrices[min];
        currentBestSell = i + 1;
        currentBestBuy = min;
    }
}

console.log(currentBestBuy);
console.log(currentBestSell);
console.log(max_profit);
Ido Weinstein
fuente
Las respuestas de solo código se desaconsejan.
Pritam Banerjee
0

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:

function maximizeProfit(array $dailyPrices)
{
    $buyDay = $sellDay = $cheaperDay = $profit = 0;

    for ($today = 0; $today < count($dailyPrices); $today++) {
        if ($dailyPrices[$today] < $dailyPrices[$cheaperDay]) {
            $cheaperDay = $today;
        } elseif ($dailyPrices[$today] - $dailyPrices[$cheaperDay] > $profit) {
            $buyDay  = $cheaperDay;
            $sellDay = $today;
            $profit   = $dailyPrices[$today] - $dailyPrices[$cheaperDay];
        }
    }
    return [$buyDay, $sellDay];
}
Natxet
fuente
0

Una solución ordenada:

+ (int)maxProfit:(NSArray *)prices {
    int maxProfit = 0;

    int bestBuy = 0;
    int bestSell = 0;
    int currentBestBuy = 0;

    for (int i= 1; i < prices.count; i++) {
        int todayPrice = [prices[i] intValue];
        int bestBuyPrice = [prices[currentBestBuy] intValue];
        if (todayPrice < bestBuyPrice) {
            currentBestBuy = i;
            bestBuyPrice = todayPrice;
        }

        if (maxProfit < (todayPrice - bestBuyPrice)) {
            bestSell = i;
            bestBuy = currentBestBuy;
            maxProfit = (todayPrice - bestBuyPrice);
        }
    }

    NSLog(@"Buy Day : %d", bestBuy);
    NSLog(@"Sell Day : %d", bestSell);

    return maxProfit;
}
Naveen Shan
fuente
0
def get_max_profit(stock):
    p=stock[0]
    max_profit=0
    maxp=p
    minp=p
    for i in range(1,len(stock)):
        p=min(p,stock[i])
        profit=stock[i]-p
        if profit>max_profit:
            maxp=stock[i]
            minp=p
            max_profit=profit
    return minp,maxp,max_profit



stock_prices = [310,315,275,295,260,270,290,230,255,250]
print(get_max_profit(stock_prices))

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) .

Arun Tom
fuente
0

Aquí está mi solución.

public static int maxProfit(List<Integer> in) {
    int min = in.get(0), max = 0;
    for(int i=0; i<in.size()-1;i++){

        min=Math.min(min, in.get(i));

        max = Math.max(in.get(i) - min, max);
     }

     return max;
 }
}
Connors
fuente
-1

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

Anikam
fuente