Massimo profitto per singola vendita


123

Supponiamo che ci venga fornito un array di n numeri interi che rappresentano i prezzi delle azioni in un singolo giorno. Vogliamo trovare una coppia (buyDay, sellDay) , con buyDay ≤ sellDay , in modo tale che se acquistassimo il titolo su buyDay e lo vendessimo su sellDay , massimizzeremmo il nostro profitto.

Chiaramente esiste una soluzione O (n 2 ) all'algoritmo provando tutte le coppie possibili (buyDay, sellDay) e prendendo il meglio da tutte. Tuttavia, esiste un algoritmo migliore, forse uno che viene eseguito in O (n) tempo?


2
Questo è il problema della sottosequenza della somma massima con un livello di riferimento indiretto.
MSN

2
@MSN: In che modo? Non guarda affatto alle somme, ma piuttosto alle differenze tra gli elementi.
PengOne

@ PengOne- Vero, ma quella domanda era chiusa. Ho riformulato la domanda per essere più facile da capire, quindi potremmo provare a tenerla aperta?
templatetypedef

2
@ PengOne, come ho detto, ha un livello di indirezione. In particolare, si desidera massimizzare la somma di guadagni / perdite su un insieme di giorni contigui. Pertanto, converti l'elenco in guadagni / perdite e trova la somma massima della sottosequenza.
MSN

1
@PDN: non funzionerà perché min può verificarsi prima di max. Non puoi vendere azioni (in questo caso) e acquistarle in seguito.
Ajeet Ganga

Risposte:


287

Amo questo problema. È una classica domanda da intervista e, a seconda di come la pensi, finirai per ottenere soluzioni sempre migliori. È certamente possibile farlo in tempi migliori rispetto a O (n 2 ) e ho elencato tre diversi modi in cui puoi pensare al problema qui. Spero che questo risponda alla tua domanda!

Primo, la soluzione divide et impera. Vediamo se possiamo risolvere questo problema dividendo l'input a metà, risolvendo il problema in ogni sottoarray, quindi combinando i due insieme. Si scopre che in realtà possiamo farlo, e possiamo farlo in modo efficiente! L'intuizione è come segue. Se abbiamo un solo giorno, l'opzione migliore è acquistare quel giorno e poi rivenderlo lo stesso giorno senza profitto. Altrimenti, dividi l'array in due metà. Se pensiamo a quale potrebbe essere la risposta ottimale, deve trovarsi in uno dei tre punti:

  1. La corretta coppia acquisto / vendita avviene completamente entro la prima metà.
  2. La corretta coppia acquisto / vendita avviene completamente entro la seconda metà.
  3. La coppia di acquisto / vendita corretta si verifica in entrambe le metà: compriamo nella prima metà, poi vendiamo nella seconda metà.

Possiamo ottenere i valori per (1) e (2) invocando ricorsivamente il nostro algoritmo sulla prima e sulla seconda metà. Per l'opzione (3), il modo per ottenere il profitto più alto sarebbe comprare nel punto più basso nella prima metà e vendere nel punto più alto nella seconda metà. Possiamo trovare i valori minimo e massimo nelle due metà semplicemente eseguendo una semplice scansione lineare sull'input e trovando i due valori. Questo poi ci dà un algoritmo con la seguente ricorrenza:

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

Usando il Teorema Master per risolvere la ricorrenza, troviamo che questo viene eseguito nel tempo O (n lg n) e utilizzerà lo spazio O (lg n) per le chiamate ricorsive. Abbiamo appena battuto l'ingenua soluzione O (n 2 )!

Ma aspetta! Possiamo fare molto meglio di così. Si noti che l'unico motivo per cui abbiamo un termine O (n) nella nostra ricorrenza è che abbiamo dovuto scansionare l'intero input cercando di trovare i valori minimo e massimo in ciascuna metà. Dato che stiamo già esplorando ricorsivamente ogni metà, forse possiamo fare di meglio facendo in modo che la ricorsione restituisca anche i valori minimo e massimo memorizzati in ciascuna metà! In altre parole, la nostra ricorsione restituisce tre cose:

  1. I tempi di acquisto e vendita per massimizzare il profitto.
  2. Il valore minimo complessivo nell'intervallo.
  3. Il valore massimo complessivo nell'intervallo.

Questi ultimi due valori possono essere calcolati ricorsivamente utilizzando una semplice ricorsione che possiamo eseguire contemporaneamente alla ricorsione per calcolare (1):

  1. I valori massimo e minimo di un intervallo di un singolo elemento sono solo quell'elemento.
  2. I valori massimo e minimo di un intervallo di elementi multipli possono essere trovati dividendo l'input a metà, trovando i valori massimo e minimo di ciascuna metà, quindi prendendo i rispettivi valori massimo e minimo.

Se usiamo questo approccio, la nostra relazione di ricorrenza è adesso

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

L'uso del Teorema Master qui ci dà un tempo di esecuzione di O (n) con O (lg n) spazio, che è persino migliore della nostra soluzione originale!

Ma aspetta un attimo: possiamo fare anche meglio di così! Pensiamo a risolvere questo problema utilizzando la programmazione dinamica. L'idea sarà di pensare al problema come segue. Supponiamo di conoscere la risposta al problema dopo aver esaminato i primi k elementi. Potremmo usare la nostra conoscenza dell'elemento (k + 1) st, combinata con la nostra soluzione iniziale, per risolvere il problema per i primi (k + 1) elementi? Se è così, potremmo ottenere un ottimo algoritmo risolvendo il problema per il primo elemento, poi i primi due, poi i primi tre, ecc. Finché non lo avessimo calcolato per i primi n elementi.

Pensiamo a come farlo. Se abbiamo un solo elemento, sappiamo già che deve essere la migliore coppia acquisto / vendita. Supponiamo ora di conoscere la risposta migliore per i primi k elementi e di guardare l'elemento (k + 1) st. Quindi l'unico modo in cui questo valore può creare una soluzione migliore di quella che avevamo per i primi k elementi è se la differenza tra il più piccolo dei primi k elementi e quel nuovo elemento è maggiore della differenza più grande che abbiamo calcolato finora. Quindi supponiamo che mentre esaminiamo gli elementi, teniamo traccia di due valori: il valore minimo che abbiamo visto finora e il profitto massimo che potremmo ottenere solo con i primi k elementi. Inizialmente, il valore minimo che abbiamo visto finora è il primo elemento e il profitto massimo è zero. Quando vediamo un nuovo elemento, per prima cosa aggiorniamo il nostro profitto ottimale calcolando quanto guadagneremmo acquistando al prezzo più basso visto finora e vendendo al prezzo corrente. Se questo è migliore del valore ottimale che abbiamo calcolato finora, aggiorniamo la soluzione ottimale in modo che sia questo nuovo profitto. Successivamente, aggiorniamo l'elemento minimo visto finora in modo che sia il minimo dell'elemento più piccolo corrente e del nuovo elemento.

Poiché ad ogni passaggio eseguiamo solo O (1) lavoro e visitiamo ciascuno degli n elementi esattamente una volta, questo richiede O (n) tempo per essere completato! Inoltre, utilizza solo la memoria ausiliaria O (1). Questo è quanto siamo arrivati ​​finora!

Ad esempio, sui tuoi input, ecco come potrebbe funzionare questo algoritmo. I numeri tra ciascuno dei valori dell'array corrispondono ai valori mantenuti dall'algoritmo in quel punto. In realtà non archivieresti tutti questi (ci vorrebbe O (n) memoria!), Ma è utile vedere l'algoritmo evolversi:

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

Risposta: (5, 10)

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

Risposta: (4, 12)

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

Risposta: (1, 5)

Possiamo fare di meglio adesso? Sfortunatamente, non in senso asintotico. Se usiamo meno di O (n) tempo, non possiamo guardare tutti i numeri su input di grandi dimensioni e quindi non possiamo garantire di non perdere la risposta ottimale (potremmo semplicemente "nasconderla" negli elementi che non ha guardato). Inoltre, non possiamo utilizzare uno spazio inferiore a O (1). Potrebbero esserci alcune ottimizzazioni ai fattori costanti nascosti nella notazione O grande, ma per il resto non possiamo aspettarci di trovare opzioni radicalmente migliori.

Nel complesso, ciò significa che abbiamo i seguenti algoritmi:

  • Ingenuo: O (n 2 ) tempo, O (1) spazio.
  • Dividi e conquista: O (n lg n) tempo, O (lg n) spazio.
  • Divide-and-Conquer ottimizzato: O (n) tempo, O (lg n) spazio.
  • Programmazione dinamica: tempo O (n), spazio O (1).

Spero che questo ti aiuti!

EDIT : Se sei interessato, ho programmato una versione Python di questi quattro algoritmi in modo che tu possa giocare con loro e giudicare le loro prestazioni relative. Ecco il codice:

# 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

1
@ FrankQ.- È necessario spazio per entrambe le chiamate ricorsive, ma in genere queste chiamate vengono eseguite una dopo l'altra. Ciò significa che il compilatore può riutilizzare la memoria tra le chiamate; una volta che una chiamata ritorna, la chiamata successiva può riutilizzare il suo spazio. Di conseguenza, è necessaria solo la memoria per contenere una chiamata di funzione alla volta, quindi l'utilizzo della memoria è proporzionale alla profondità massima dello stack di chiamate. Poiché la ricorsione termina ai livelli O (log n), è necessario utilizzare solo la memoria O (log n). Questo chiarisce le cose?
templatetypedef

Qualcuno potrebbe portarli su Ruby? Parte della ricorsione non funziona allo stesso modo di Python. Anche queste soluzioni restituiscono solo il massimo profitto; non restituiscono i punti della matrice che hanno prodotto il profitto (che potrebbe essere utilizzato per riportare la percentuale di aumento del profitto nel passato)
rcd

Il concetto di programmazione dinamica non è realmente necessario per spiegare la soluzione O (n) time, ma è fantastico che si leghi a tutti questi tipi di algoritmi.
Rn222

Come puoi costruire su uno qualsiasi degli algoritmi sub O (n ^ 2) per trovare tutte le coppie ordinate per profitto?
ferk86

@templatetypedef come cambieremmo l'approccio della programmazione dinamica se dovessimo iniziare con un budget di M $ e invece di una singola azione avessimo m azioni con prezzi su n giorni come indicato? vale a dire, variando il numero di azioni acquistate e i dati di borsa disponibili da 1 a n stock (come prima, avevamo solo per Google, ora abbiamo anche per altre 5 società)
Ronak Agrawal

32

Questo è il problema della sottosequenza della somma massima con un po 'di indirezione. Al problema della sottosequenza della somma massima viene fornito un elenco di numeri interi che potrebbero essere positivi o negativi, trovare la somma più grande di un sottoinsieme contiguo di quella lista.

Puoi banalmente convertire questo problema in quel problema prendendo il profitto o la perdita tra giorni consecutivi. Quindi trasformereste un elenco dei prezzi delle azioni, ad esempio [5, 6, 7, 4, 2]in un elenco di guadagni / perdite, ad esempio [1, 1, -3, -2]. Il problema della somma delle sottosequenze è quindi abbastanza facile da risolvere: trova la sottosequenza con la somma più grande di elementi in un array


1
Non credo che tutto funziona in questo modo, dal momento che se si acquista il titolo su qualche giorno prima non lo fai maturare i benefici del delta del giorno precedente. O non è questo un problema in questo approccio?
templatetypedef

1
@templatetypedef, ecco perché tieni traccia della somma più grande e della somma della sequenza corrente. Quando la somma della sequenza corrente scende sotto lo zero, sai che non avrai fatto soldi con quella sequenza e puoi ricominciare da capo. Tracciando la somma più grande, troverai automaticamente le migliori date di acquisto / vendita.
MSN

6
@templatetypedef, per inciso, fai la stessa cosa nella tua risposta.
MSN

16

Non sono proprio sicuro del motivo per cui questa è considerata una domanda di programmazione dinamica. Ho visto questa domanda nei libri di testo e nelle guide degli algoritmi che utilizzano il runtime O (n log n) e O (log n) per lo spazio (ad esempio Elements of Programming Interviews). Sembra un problema molto più semplice di quello che le persone pensano che sia.

Funziona tenendo traccia del profitto massimo, del prezzo di acquisto minimo e, di conseguenza, del prezzo di acquisto / vendita ottimale. Mentre passa attraverso ogni elemento dell'array, controlla se l'elemento dato è inferiore al prezzo di acquisto minimo. In tal caso, l'indice del prezzo di acquisto minimo, ( min), viene aggiornato per essere l'indice di quell'elemento. Inoltre, per ogni elemento, l' becomeABillionairealgoritmo controlla se arr[i] - arr[min](la differenza tra l'elemento corrente e il prezzo di acquisto minimo) è maggiore del profitto corrente. Se lo è, il profitto viene aggiornato a quella differenza e l'acquisto è impostato su arr[min]e la vendita è impostata su arr[i].

Funziona in un unico passaggio.

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

}

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


2

Il problema è identico alla sub-sequenza massima che
ho risolto utilizzando la programmazione dinamica. Tieni traccia della data corrente e precedente (profitto, acquisto e data di vendita) Se la corrente è superiore alla precedente, sostituisci la precedente con la corrente.

    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 );
    }   

2

ecco la mia soluzione 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);     
}

@ Nitiraj, sì, questa soluzione è corretta ma ti chiedo di leggere la risposta fornita da templatetypedef, poiché nella risposta fornita da templatetypedef, vengono menzionate tutte le possibili soluzioni inclusa quella pubblicata da Rohit. La soluzione di Rohit è in realtà un'implementazione dell'ultima soluzione con O (n) utilizzando la programmazione dinamica menzionata nella risposta fornita da templatetypedef.
nits.kk

1
Supponi che il tuo array sia int A [] = {5, 4, 6, 7, 6, 3, 2, 5}; Quindi, secondo la tua logica, comprerai a indice 6 e poi lo venderai a indice 3. Il che è sbagliato. Non puoi vendere in passato. L'indice di vendita deve essere maggiore dell'indice di acquisto.
sviluppatore

1
La soluzione sopra è "quasi" corretta. ma stampa l'indice minimo assoluto invece dell'indice del prezzo di "acquisto". Per correggere, hai bisogno di un'altra variabile, ad esempio minBuyIndex che aggiorni solo all'interno del blocco "if (profit> maxProfit)" e stampala.
javabrew

1

Ho trovato una soluzione semplice: il codice è più autoesplicativo. È una di quelle domande sulla programmazione dinamica.

Il codice non si occupa del controllo degli errori e dei casi limite. È solo un esempio per dare l'idea della logica di base per risolvere il 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();
        }
    }
}

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;

    }

Ecco la mia soluzione. modifica l'algoritmo di sub-sequenza massima. Risolve il problema in O (n). Penso che non possa essere fatto più velocemente.


1

Questo è un problema interessante, perché sembra difficile, ma una riflessione attenta produce una soluzione elegante ed essenziale.

Come è stato notato, può essere risolto con la forza bruta in tempo O (N ^ 2). Per ogni voce nella matrice (o elenco), iterare su tutte le voci precedenti per ottenere il minimo o il massimo a seconda che il problema sia trovare il guadagno o la perdita maggiore.

Ecco come pensare a una soluzione in O (N): ogni voce rappresenta un nuovo possibile massimo (o minimo). Quindi, tutto ciò che dobbiamo fare è salvare il minimo precedente (o massimo) e confrontare il diff con il minimo corrente e il precedente (o massimo). Vai tranquillo.

Ecco il codice, in Java come test 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;
    }

}

Nel caso di calcolo della perdita maggiore, teniamo traccia del massimo nel listino (prezzo di acquisto) fino alla voce corrente. Quindi calcoliamo la differenza tra la voce massima e quella corrente. Se max - current> maxLoss, manteniamo questo diff come il nuovo maxLoss. Poiché l'indice di max è garantito essere inferiore all'indice di corrente, garantiamo che la data di "acquisto" è inferiore alla data di "vendita".

Nel caso del calcolo del guadagno maggiore, tutto è invertito. Teniamo traccia del minimo nell'elenco fino alla voce corrente. Calcoliamo il diff tra il minimo e la voce corrente (invertendo l'ordine nella sottrazione). Se current - min> maxGain, manteniamo questo diff come il nuovo maxGain. Anche in questo caso, l'indice di "acquisto" (min) viene prima dell'indice di corrente ("vendita").

Dobbiamo solo tenere traccia del maxGain (o maxLoss) e dell'indice di min o max, ma non di entrambi, e non abbiamo bisogno di confrontare gli indici per confermare che 'buy' è minore di 'sell', poiché noi ottenere questo naturalmente.


1

Massimo profitto per singola vendita, soluzione 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
}

Ecco un progetto che esegue test di complessità temporale su approcci o (N) vs o (n ^ 2) su un set di dati casuali su 100k int. O (n ^ 2) richiede 2 secondi, mentre O (n) richiede 0,01 secondi

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
}

Questo è l'approccio più lento, o (n ^ 2) che scorre nel resto dei giorni per ogni giorno, doppio ciclo.


1

La risposta più votata non consente casi in cui il profitto massimo è negativo e dovrebbe essere modificata per consentire tali casi. Si può farlo limitando l'intervallo del ciclo a (len (a) - 1) e cambiando il modo in cui il profitto è determinato spostando l'indice di 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

Confronta questa versione della funzione con la precedente per l'array:

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

singSellProfit(s)
-1

DynamicProgrammingSingleSellProfit(s)
0

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");
}

0

Una possibilità per determinare il profitto massimo potrebbe essere quella di tenere traccia degli elementi minimo del lato sinistro e massimo del lato destro nella matrice in corrispondenza di ciascun indice nella matrice. Quando stai quindi iterando i prezzi delle azioni, per un dato giorno conoscerai il prezzo più basso fino a quel giorno e conoscerai anche il prezzo massimo dopo (e incluso) quel giorno.

Ad esempio, definiamo un min_arrand max_arr, con l'array dato che è arr. L'indice iin min_arrsarebbe l'elemento minimo in arrper tutti gli indici <= i(a sinistra di e incluso i). L'indice iin max_arrsarebbe l'elemento massimo in arrper tutti gli indici >= i(diritto di e incluso i). Quindi, potresti trovare la differenza massima tra gli elementi corrispondenti in max_arre `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

Questo dovrebbe essere eseguito in O (n) tempo, ma credo che utilizzi molto spazio.


0

Questa è la differenza massima tra due elementi nell'array e questa è la mia soluzione:

O (N) complessità temporale O (1) complessità spaziale

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

0

Dopo aver fallito in un esame di codifica dal vivo per una posizione di ingegnere di soluzioni FB, ho dovuto risolverlo in un'atmosfera calma e fresca, quindi ecco i miei 2 centesimi:

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

Le risposte solo al codice sono scoraggiate.
Pritam Banerjee

0

L'unica risposta che risponde veramente alla domanda è quella di @akash_magoon (e in modo così semplice!), Ma non restituisce esattamente l'oggetto specificato nella domanda. Ho modificato un po 'e ho la mia risposta in PHP che restituisce proprio ciò che viene chiesto:

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];
}

0

Una soluzione chiara:

+ (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;
}

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

Questo programma in python3 può restituire il prezzo di acquisto e il prezzo di vendita che massimizzerà il profitto, calcolato con la complessità temporale di O (n) e la complessità spaziale di O (1) .


0

Ecco la mia soluzione

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;
 }
}

-1

Per tutte le risposte che tengono traccia degli elementi minimo e massimo, quella soluzione è in realtà una soluzione O (n ^ 2). Questo perché alla fine bisogna verificare se il massimo è avvenuto dopo il minimo oppure no. In caso contrario, sono necessarie ulteriori iterazioni finché quella condizione non viene soddisfatta, e questo lascia un caso peggiore di O (n ^ 2). E se vuoi saltare le iterazioni extra, è necessario molto più spazio. In ogni caso, un no-no rispetto alla soluzione di programmazione dinamica

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.