Maksimum tek satış karı


123

Tek bir günde hisse senedi fiyatlarını temsil eden bir dizi n tamsayı verildiğini varsayalım . BuyDay ≤ sellDay ile bir çift (buyDay, sellDay) bulmak istiyoruz , öyle ki eğer buyDay'de hisse senedini alıp sellDay'de satarsak kârımızı maksimize etmiş oluruz.

Açıkçası, tüm olası (buyDay, sellDay) çiftleri deneyerek ve hepsinden en iyisini alarak algoritmaya bir O (n 2 ) çözümü vardır. Ancak, daha iyi bir algoritma var mı, belki de O (n) zamanında çalışan bir algoritma ?


2
Bu, dolaylı seviyeye sahip maksimum toplam alt dizi problemidir.
MSN

2
@MSN: Nasıl yani? Toplamlara bakmıyor, daha çok öğeler arasındaki farklılıklara bakıyor.
PengOne

@ PengOne- Doğru, ancak bu soru kapatıldı. Daha kolay anlaşılması için soruyu yeniden ifade ettim, bu yüzden bunu açık tutmaya çalışabilir miyiz?
templatetypedef

2
@PengOne, Dediğim gibi, tek bir yönlendirme seviyesi var. Özellikle, birbirini takip eden günler boyunca kazanç / kayıp toplamını maksimize etmek istersiniz. Bu nedenle, listeyi kazançlara / kayıplara dönüştürün ve maksimum alt dizi toplamını bulun.
MSN

1
@PDN: Bu işe yaramaz çünkü min, maks. Hisse senedi satamazsınız (bu durumda) ve daha sonra satın alamazsınız.
Ajeet Ganga

Yanıtlar:


287

Bu sorunu seviyorum. Klasik bir mülakat sorusudur ve bunun hakkında nasıl düşündüğünüze bağlı olarak, daha iyi ve daha iyi çözümler elde edeceksiniz. Bunu O'dan daha iyi yapmak kesinlikle mümkündür (n 2 ) süresinden ve burada problem hakkında düşünebileceğiniz üç farklı yol listeledim. Umarım bu, sorunuzu yanıtlar!

İlk olarak, böl ve yönet çözümü. Bakalım girdiyi ikiye bölerek, her bir alt dizideki problemi çözerek ve sonra ikisini birleştirerek bunu çözebilir miyiz görelim. Bunu gerçekten yapabileceğimiz ve bunu verimli bir şekilde yapabileceğimiz ortaya çıktı! Sezgi aşağıdaki gibidir. Tek bir günümüz varsa, en iyi seçenek o gün satın almak ve sonra aynı gün kar amacı gütmeden geri satmaktır. Aksi takdirde, diziyi ikiye bölün. En uygun cevabın ne olabileceğini düşünürsek, üç yerden birinde olmalıdır:

  1. Doğru alım / satım çifti tamamen ilk yarıda gerçekleşir.
  2. Doğru alım / satım çifti tamamen ikinci yarıda gerçekleşir.
  3. Doğru alım / satım çifti her iki yarıda da oluşur - ilk yarıda alırız, sonra ikinci yarıda satarız.

Algoritmamızı birinci ve ikinci yarıda özyinelemeli olarak çağırarak (1) ve (2) değerlerini alabiliriz. Seçenek (3) için, en yüksek karı elde etmenin yolu, ilk yarıda en düşük noktada satın almak ve ikinci yarıda en yüksek noktada satmak olacaktır. İki yarıdaki minimum ve maksimum değerleri, giriş üzerinden basit bir doğrusal tarama yaparak ve iki değeri bularak bulabiliriz. Bu bize aşağıdaki yinelemeye sahip bir algoritma verir:

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

Yinelemeyi çözmek için Ana Teoremi kullanarak , bunun O (n lg n) zamanda çalıştığını ve özyinelemeli çağrılar için O (lg n) uzayını kullanacağını bulduk. Az önce saf O (n 2 ) çözümünü yendik!

Fakat bekle! Bundan daha iyisini yapabiliriz. Yinelememizde bir O (n) terimine sahip olmamızın tek nedeninin, her bir yarıdaki minimum ve maksimum değerleri bulmaya çalışırken tüm girdiyi taramamız gerektiğine dikkat edin. Halihazırda her bir yarıyı yinelemeli olarak araştırdığımız için, özyinelemenin her yarımda saklanan minimum ve maksimum değerleri de geri vermesini sağlayarak belki daha iyisini yapabiliriz! Başka bir deyişle, özyinelememiz üç şeyi geri getiriyor:

  1. Kârı maksimize etmek için alım satım süreleri.
  2. Aralıktaki genel minimum değer.
  3. Aralıktaki toplam maksimum değer.

Bu son iki değer, hesaplamak için özyineleme ile aynı anda çalıştırabileceğimiz basit bir özyineleme kullanılarak özyinelemeli olarak hesaplanabilir (1):

  1. Tek öğeli bir aralığın maksimum ve minimum değerleri yalnızca bu öğedir.
  2. Çoklu eleman aralığının maksimum ve minimum değerleri, girişi ikiye bölerek, her bir yarının maksimum ve minimum değerlerini bularak ve ardından ilgili maksimum ve minimum değerlerini alarak bulunabilir.

Bu yaklaşımı kullanırsak, tekrarlama ilişkimiz şimdi

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

Buradaki Ana Teoremi kullanmak bize O (lg n) uzayıyla O (n) çalışma zamanı verir, ki bu orijinal çözümümüzden bile daha iyidir!

Ama bir dakika bekleyin - bundan daha iyisini yapabiliriz! Dinamik programlama kullanarak bu problemi çözmeyi düşünelim. Fikir, sorunu aşağıdaki gibi düşünmek olacaktır. İlk k öğelerine baktıktan sonra sorunun cevabını bildiğimizi varsayalım. Problemi ilk (k + 1) elementler için çözmek için (k + 1) st element bilgimizi ilk çözümümüzle birleştirebilir miyiz? Eğer öyleyse, ilk n eleman için hesaplayana kadar ilk eleman, sonra ilk ikisini, sonra ilk üçünü vs. çözerek harika bir algoritma elde edebilirdik.

Bunun nasıl yapılacağını düşünelim. Sadece bir elementimiz varsa, bunun en iyi alış / satış çifti olması gerektiğini zaten biliyoruz. Şimdi, ilk k elementi için en iyi cevabı bildiğimizi ve (k + 1) st elementine baktığımızı varsayalım. O halde, bu değerin ilk k elemanlar için sahip olduğumuzdan daha iyi bir çözüm oluşturmasının tek yolu, ilk k elemanlarının en küçüğü ile bu yeni eleman arasındaki farkın şimdiye kadar hesapladığımız en büyük farktan daha büyük olmasıdır. Öyleyse, unsurların üzerinden geçerken, iki değeri takip ettiğimizi varsayalım - şimdiye kadar gördüğümüz minimum değer ve sadece ilk k elementiyle elde edebileceğimiz maksimum kar. Başlangıçta, şimdiye kadar gördüğümüz minimum değer ilk unsurdur ve maksimum kar sıfırdır. Yeni bir unsur gördüğümüzde, ilk önce o ana kadar görülen en düşük fiyattan satın alıp mevcut fiyattan satış yaparak ne kadar kazanacağımızı hesaplayarak optimum karımızı güncelliyoruz. Bu, şimdiye kadar hesapladığımız optimum değerden daha iyiyse, en uygun çözümü bu yeni kâr olacak şekilde güncelliyoruz. Daha sonra, şimdiye kadar görülen minimum elemanı, mevcut en küçük elemanın ve yeni elemanın minimumu olacak şekilde güncelliyoruz.

Her adımda yalnızca O (1) çalışması yaptığımız ve n öğenin her birini tam olarak bir kez ziyaret ettiğimiz için, bu işlemin tamamlanması O (n) zaman alır! Ayrıca, yalnızca O (1) yardımcı depolamayı kullanır. Bu şimdiye kadar aldığımız kadar iyi!

Örnek olarak, girdilerinizde bu algoritmanın nasıl çalışabileceği aşağıda açıklanmıştır. Dizinin değerlerinin her biri arasındaki sayılar, o noktada algoritma tarafından tutulan değerlere karşılık gelir. Aslında bunların hepsini saklamazsınız (O (n) bellek alır!), Ancak algoritmanın geliştiğini görmek faydalı olacaktır:

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

Cevap: (5, 10)

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

Cevap: (4, 12)

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

Cevap: (1, 5)

Şimdi daha iyisini yapabilir miyiz? Ne yazık ki, asimptotik anlamda değil. O (n) süresinden daha az kullanırsak, büyük girdilerdeki tüm sayılara bakamayız ve bu nedenle en uygun yanıtı kaçırmayacağımızı garanti edemeyiz (bunu yalnızca öğelerde "gizleyebiliriz" bakmadım). Ayrıca, O (1) alanından daha azını kullanamayız. Big-O gösteriminde saklı sabit faktörlerde bazı optimizasyonlar olabilir, ancak aksi takdirde radikal olarak daha iyi seçenekler bulmayı bekleyemeyiz.

Genel olarak bu, aşağıdaki algoritmalara sahip olduğumuz anlamına gelir:

  • Naif: O (n 2 ) zaman, O (1) uzay.
  • Böl ve Yönet: O (n lg n) zaman, O (lg n) uzay.
  • Optimize Edilmiş Böl ve Yönet: O (n) zamanı, O (lg n) uzayı.
  • Dinamik programlama: O (n) zaman, O (1) uzay.

Bu yardımcı olur umarım!

DÜZENLEME : İlgileniyorsanız, bu dört algoritmanın bir Python versiyonunu kodladım, böylece onlarla oynayabilir ve göreceli performanslarını değerlendirebilirsiniz. İşte kod:

# 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.- Her iki özyinelemeli çağrı için boşluk gereklidir, ancak tipik olarak bu çağrılar birbiri ardına gerçekleştirilir. Bu, derleyicinin çağrılar arasında belleği yeniden kullanabileceği anlamına gelir; Bir çağrı geri döndüğünde, sonraki çağrı kendi alanını yeniden kullanabilir. Sonuç olarak, bir seferde yalnızca bir işlev çağrısını tutmak için belleğe ihtiyacınız vardır, bu nedenle bellek kullanımı çağrı yığınının maksimum derinliğiyle orantılıdır. Özyineleme O (log n) seviyelerinde sona erdiğinden, sadece O (log n) belleğinin kullanılması gerekir. Bu işleri netleştirir mi?
templatetypedef

Bunları Ruby'ye taşıyan biri olabilir mi? Özyinelemenin bir kısmı Python'daki gibi çalışmaz. Ayrıca bu çözümler yalnızca maksimum kârı döndürür; karı getiren dizi noktalarını döndürmezler (geçmişte kar artışının yüzdesini bildirmek için kullanılabilir)
rcd

Dinamik programlama kavramı, O (n) zamanı çözümünü açıklamak için gerçekten gerekli değildir, ancak tüm bu tür algoritmaları bağlamanız harikadır.
Rn222

Kâra göre sıralanmış tüm çiftleri bulmak için herhangi bir alt O (n ^ 2) algoritmasını nasıl inşa edebilirsiniz?
ferk86

@templatetypedef M $ 'lık bir bütçeyle başlayacak olsaydık ve tek hisse senedi yerine, n günden fazla fiyata sahip m hisse senedimiz olsaydı, dinamik programlama yaklaşımını nasıl değiştirirdik? yani, satın alınan hisse senedi sayısını ve mevcut stok verilerini 1 hisse senedinden n hisse senedine değiştirdik (daha önce olduğu gibi, şimdi sadece Google için vardı, şimdi 5 başka şirket için de var)
Ronak Agrawal

32

Bu, biraz dolaylı maksimum toplam alt dizi problemidir. Maksimum toplam alt dizi problemine pozitif veya negatif olabilen bir tamsayı listesi verilir, bu listenin bitişik bir alt kümesinin en büyük toplamını bulur.

Birbirini izleyen günler arasında kar veya zararı alarak bu sorunu önemsiz bir şekilde bu soruna dönüştürebilirsiniz. Böylece bir hisse senedi fiyatları listesini, örneğin [5, 6, 7, 4, 2]bir kazanç / kayıp listesine, örneğin, dönüştürebilirsiniz [1, 1, -3, -2]. Alt dizi toplamı probleminin çözülmesi oldukça kolaydır: Bir dizideki en büyük eleman toplamına sahip alt diziyi bulun


1
Ben sanmıyorum oldukça Önceki gün gelen delta yararları tahakkuk yok bazı ilk gününde stok satın alırsanız, çünkü bu şekilde çalışır. Yoksa bu yaklaşımda sorun değil mi?
templatetypedef

1
@templatetypedef, bu yüzden en büyük toplamı ve mevcut sıra toplamını takip ediyorsunuz. Mevcut sıra toplamı sıfırın altına düştüğünde, bu sıra ile hiç para kazanmayacağınızı bilirsiniz ve yeniden başlayabilirsiniz. En büyük toplamı takip ederek, en iyi alım / satım tarihlerini otomatik olarak bulacaksınız.
MSN

6
@templatetypedef, tesadüfen, cevabınızda aynı şeyi yapıyorsunuz.
MSN

16

Bunun neden dinamik bir programlama sorusu olarak kabul edildiğinden emin değilim. Bu soruyu ders kitaplarında ve alan için O (n log n) çalışma zamanı ve O (log n) kullanan algoritma kılavuzlarında görmüştüm (örneğin Programlama Görüşmelerinin Öğeleri). İnsanların sandığından çok daha basit bir sorun gibi görünüyor.

Bu, maksimum karı, minimum alış fiyatını ve dolayısıyla en uygun alış / satış fiyatını takip ederek çalışır. Dizideki her bir elemanın üzerinden geçerken, verilen elemanın minimum satın alma fiyatından küçük olup olmadığını kontrol eder. Öyleyse, minimum satın alma fiyatı endeksi ( min), o öğenin endeksi olacak şekilde güncellenir. Ek olarak, her bir öğe için, becomeABillionairealgoritma arr[i] - arr[min](mevcut öğe ile minimum satın alma fiyatı arasındaki fark) mevcut kardan daha büyük olup olmadığını kontrol eder. Öyleyse, kar bu farka göre güncellenir ve satın alma olarak ayarlanır arr[min]ve satış olarak ayarlanır arr[i].

Tek geçişte çalışır.

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

}

Ortak yazar: https://stackoverflow.com/users/599402/ephraim


2

Sorun,
Dinamik programlama kullanarak çözdüğüm maksimum alt diziyle aynı . Mevcut ve öncekileri takip edin (Kar, satın alma ve satış tarihi) Eğer akım öncekinden yüksekse öncekini mevcut ile değiştirin.

    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

İşte Java çözümüm:

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, evet bu çözüm doğru ancak templatetypedef tarafından sağlanan yanıtı okumanızı rica ediyorum, templatetypedef tarafından sağlanan yanıtta olduğu gibi, Rohit tarafından gönderilenler de dahil olmak üzere tüm olası çözümlerden bahsediliyor. Rohit'in çözümü aslında templatetypedef tarafından sağlanan cevapta belirtilen dinamik programlamayı kullanan O (n) ile son çözümün uygulanmasıdır.
nits.kk

1
Dizinizin int A [] = {5, 4, 6, 7, 6, 3, 2, 5} olduğunu varsayalım; Sonra mantığınıza göre indeks 6'dan satın alıp 3 indeksinden satacaksınız. Bu yanlış. Geçmişte satış yapamazsınız. Satış endeksi, alış endeksinden daha büyük olmalıdır.
developer747

1
Yukarıdaki çözüm "neredeyse" doğrudur. ancak "alış" fiyatının endeksi yerine mutlak minimum endeksi yazdırır. Düzeltmek için başka bir değişkene ihtiyacınız var, mesela minBuyIndex, bunu sadece "if (kar> maxProfit)" bloğu içinde güncelleyin ve yazdırın.
javabrew

1

Basit bir çözüm buldum - kod daha açıklayıcıdır. Bu dinamik programlama sorusundan biridir.

Kod, hata kontrolü ve uç durumlarla ilgilenmez. Problemi çözmek için temel mantık fikrini vermek için sadece bir örnek.

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;

    }

İşte benim çözümüm. maksimum alt sıra algoritmasını değiştirir. O (n) 'deki problemi çözer. Daha hızlı yapılamayacağını düşünüyorum.


1

Bu ilginç bir sorundur, çünkü zor görünmektedir , ancak dikkatlice düşünmek zarif ve ayrıştırılmış bir çözüm sağlar.

Belirtildiği gibi, kaba kuvvet O (N ^ 2) zamanında çözülebilir. Dizideki (veya listedeki) her giriş için, sorunun en büyük kazancı veya kaybı bulup bulmadığına bağlı olarak minimum veya maksimum değeri elde etmek için önceki tüm girişleri yineleyin.

İşte O (N) cinsinden bir çözüm hakkında nasıl düşüneceğiniz: her giriş yeni bir olası maks (veya min) temsil eder. Ardından, tek yapmamız gereken önceki min (veya maks) değerini kaydetmek ve farkı akım ve önceki min (veya maks) ile karşılaştırmak. Tereyağından kıl çeker gibi.

JUnit testi olarak Java'da kod şu şekildedir:

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 büyük zararı hesaplama durumunda, listedeki maksimum değeri (alış fiyatı) mevcut girişe kadar takip ederiz. Daha sonra maksimum ve mevcut giriş arasındaki farkı hesaplıyoruz. Max - current> maxLoss ise, bu diff'i yeni maxLoss olarak tutuyoruz. Maksimum endeksinin mevcut endeksten daha az olması garantili olduğundan, 'alış' tarihinin 'satış' tarihinden daha az olmasını garanti ediyoruz.

En büyük kazancı hesaplama durumunda her şey tersine çevrilir. Güncel girişe kadar listedeki min sayısını takip ediyoruz. Min ve mevcut giriş arasındaki farkı hesaplıyoruz (çıkarma sırasını tersine çevirerek). Eğer current - min> maxGain ise, bu farkı yeni maxGain olarak tutuyoruz. Yine, 'alış' (min) endeksi, cari ('sat') endeksinden önce gelir.

Yalnızca maxGain (veya maxLoss) ve min veya max endeksini takip etmemiz gerekiyor, ancak ikisini birden değil ve 'satın al' ifadesinin 'sat' değerinden daha az olduğunu doğrulamak için endeksleri karşılaştırmamıza gerek yok çünkü bunu doğal olarak al.


1

Maksimum tek satış karı, O (n) çözüm

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
}

100k ints üzerinde rastgele bir veri kümesi üzerinde o (N) ve o (n ^ 2) yaklaşımları üzerinde zaman karmaşıklığı testi yapan bir proje. O (n ^ 2) 2 saniye sürerken O (n) 0.01 saniye sürer

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
}

Bu, her gün için geri kalan günlerde çift döngü oluşturan daha yavaş, o (n ^ 2) yaklaşımdır.


1

En çok oylanan yanıt, maksimum kârın negatif olduğu durumlara izin vermez ve bu tür durumlara izin verecek şekilde değiştirilmelidir. Döngünün aralığını (len (a) - 1) ile sınırlayarak ve endeksi bir kaydırarak kârın belirlenme şeklini değiştirerek bunu yapabilirsiniz.

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

İşlevin bu sürümünü dizi için öncekiyle karşılaştırın:

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

Maksimum karı belirleme olasılığı, dizideki her dizinde dizideki sol taraf minimum ve sağ taraf maksimum öğelerini takip etmek olabilir. Daha sonra hisse senedi fiyatları üzerinden yinelediğinizde, herhangi bir gün için o güne kadarki en düşük fiyatı bileceksiniz ve ayrıca o günden sonraki (ve dahil) maksimum fiyatı da bileceksiniz.

Örneğin , verilen dizi ile bir min_arrve tanımlayalım . İndeks , tüm indeksler için minimum öğe olacaktır (i'nin solunda ve içinde dahil). İndeks , tüm indeksler için maksimum öğe olacaktır (i'nin hakkı ve i dahil). Ardından, ve "min_arr" içindeki karşılık gelen öğeler arasındaki maksimum farkı bulabilirsin :max_arrarrimin_arrarr<= iimax_arrarr>= imax_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

Bu O (n) zamanda çalışmalı, ancak çok fazla alan kullandığına inanıyorum.


0

Bu, dizideki iki öğe arasındaki maksimum fark ve bu benim çözümüm:

O (N) zaman karmaşıklığı O (1) uzay karmaşıklığı

    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

Bunu bir FB çözüm mühendisi pozisyonu için canlı bir kodlama sınavında başarısız olduktan sonra, onu sakin ve serin bir atmosferde çözmek zorunda kaldım, işte 2 sentim:

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

Yalnızca kod yanıtları önerilmez.
Pritam Banerjee

0

Soruyu gerçekten cevaplayan tek cevap @akash_magoon'dur (ve bu kadar basit bir şekilde!), Ancak soruda belirtilen tam nesneyi döndürmez. Biraz yeniden düzenledim ve PHP'deki cevabımın sorulanı döndürmesini sağladım:

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

Temiz bir çözüm:

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

Python3'teki bu program , O (n) Zaman karmaşıklığı ve O (1) Uzay karmaşıklığı ile hesaplanan, kârı en üst düzeye çıkaracak alış fiyatı ve satış fiyatını döndürebilir .


0

İşte benim çözümüm

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

Minimum ve maksimum unsurları takip eden tüm cevaplar için, bu çözüm aslında bir O (n ^ 2) çözümüdür. Bunun nedeni, sonunda maksimumun minimumdan sonra olup olmadığının kontrol edilmesi gerektiğidir. Aksi takdirde, bu koşul karşılanana kadar daha fazla yineleme gerekir ve bu, en kötü durumda O (n ^ 2) bırakır. Ve ekstra yinelemeleri atlamak istiyorsanız, çok daha fazla alan gerekir. Her iki durumda da, dinamik programlama çözümüne kıyasla hayır-hayır

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.