Максимальний прибуток від одного продажу


123

Припустимо, нам дано масив із n цілих чисел, що представляють ціни акцій за один день. Ми хочемо знайти пару (buyDay, sellDay) , з buyDay ≤ sellDay , таким чином, якби ми купили акцію на buyDay і продали її на sellDay , ми максимізували б наш прибуток.

Зрозуміло, що в алгоритмі є рішення O (n 2 ) , випробувавши всі можливі пари (buyDay, sellDay) та взявши найкраще з усіх. Однак чи є кращий алгоритм, можливо, той, який працює в O (n) час?


2
Це проблема максимальної сукупності сукупності з рівнем непрямості.
MSN

2
@MSN: Як це? Він зовсім не дивиться на суми, а навпаки, на відмінності між елементами.
PengOne

@ PengOne- Щоправда, але це питання було закрито. Я переформулював питання, щоб було легше зрозуміти, щоб ми могли спробувати тримати це відкритим?
templatetypedef

2
@PengOne, Як я вже сказав, він має один рівень непрямості. Зокрема, ви хочете максимально збільшити суму прибутку / збитку за суміжний набір днів. Тому перетворіть список у прибутки / збитки та знайдіть максимальну суму підпорядкованості.
MSN

1
@PDN: Це не спрацює, оскільки до макс. Ви не можете продати акції (в даному випадку), а придбати їх згодом.
Ajeet Ganga

Відповіді:


287

Я люблю цю проблему. Це класичне запитання про інтерв'ю, і залежно від того, як ви про це думаєте, ви в кінцевому підсумку отримаєте все кращі та кращі рішення. Це, безумовно, можливо, зробити це краще, ніж O (n 2 ), і я перерахував три різні способи, які ви можете думати про цю проблему тут. Сподіваємось, це відповідає на ваше запитання!

По-перше, рішення розділити і перемогти. Давайте подивимось, чи зможемо ми це вирішити, розділивши вхід навпіл, вирішивши задачу в кожному підряді, а потім об'єднавши два разом. Виявляється, ми насправді можемо це зробити і можемо зробити це ефективно! Інтуїція така. Якщо у нас є один день, найкращий варіант - придбати в той день, а потім продати його в той же день без прибутку. В іншому випадку розділіть масив на дві половини. Якщо ми подумаємо, якою може бути оптимальна відповідь, вона повинна бути в одному з трьох місць:

  1. Правильна пара купівлі-продажу відбувається повністю протягом першої половини.
  2. Правильна пара купівлі-продажу відбувається повністю протягом другої половини.
  3. Правильна пара купівлі / продажу відбувається в обох половинах - ми купуємо в першій половині, потім продаємо в другій половині.

Ми можемо отримати значення для (1) та (2) шляхом рекурсивного виклику нашого алгоритму на першій та другій половинах. Для варіанта (3) способом отримання найбільшого прибутку було б придбати в найнижчій точці першої половини і продати в найбільшій точці у другій половині. Ми можемо знайти мінімальні та максимальні значення у двох половинах, просто зробивши просте лінійне сканування на вході та знайшовши два значення. Потім це дає нам алгоритм із наступним повторенням:

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

Використовуючи головну теорему для вирішення рецидиву, ми виявляємо, що це працює в O (n lg n) час і буде використовувати O (lg n) простір для рекурсивних викликів. Ми щойно побили наївне рішення O (n 2 )!

Але зачекайте! Ми можемо зробити набагато краще, ніж це. Зауважте, що єдиною причиною, коли у нас є повтор O (n), є те, що нам довелося сканувати весь вхід, намагаючись знайти мінімальні та максимальні значення у кожній половині. Оскільки ми вже рекурсивно досліджуємо кожну половину, можливо, ми можемо зробити краще, якщо рекурсія також передасть мінімальні та максимальні значення, що зберігаються в кожній половині! Іншими словами, наша рекурсія передає три речі:

  1. Часи купівлі та продажу дозволяють отримати максимальний прибуток.
  2. Загальне мінімальне значення в діапазоні.
  3. Максимальне загальне значення в діапазоні.

Ці два останніх значення можна обчислити рекурсивно, використовуючи пряму рекурсію, яку ми можемо запустити одночасно з рекурсією для обчислення (1):

  1. Максимальні та мінімальні значення одноелементного діапазону - саме цей елемент.
  2. Значення max та min для декількох діапазонів елементів можна знайти, розділивши вхід навпіл, знайшовши значення max та min кожної половини, а потім взявши їх відповідні max та min.

Якщо ми будемо використовувати цей підхід, то наше відношення зараз є

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

Використання тут теореми магістра дає нам час виконання O (n) з O (lg n) простором, що навіть краще, ніж наше оригінальне рішення!

Але почекай хвилинку - ми можемо зробити навіть краще, ніж це! Давайте подумаємо над вирішенням цієї проблеми за допомогою динамічного програмування. Ідея полягає в тому, щоб думати про проблему наступним чином. Припустимо, що ми знали відповідь на проблему після перегляду перших k елементів. Чи можемо ми використати наші знання про (k + 1)-й елемент у поєднанні з нашим початковим рішенням для вирішення задачі для перших (k + 1) елементів? Якщо так, ми можемо отримати чудовий алгоритм, вирішивши задачу для першого елемента, потім перших двох, потім перших трьох тощо, поки ми не обчислимо його для перших n елементів.

Давайте подумаємо, як це зробити. Якщо у нас є лише один елемент, ми вже знаємо, що це повинна бути найкраща пара купівлі / продажу. Тепер припустимо, що ми знаємо найкращу відповідь на перші k елементи та подивимось на (k + 1) st-елемент. Тоді єдиний спосіб, що це значення може створити рішення краще, ніж те, що ми мали для перших k елементів, це якщо різниця між найменшими першими k елементами і цим новим елементом більша за найбільшу різницю, яку ми обчислили досі. Отже, припустимо, що, переходячи по елементах, ми відслідковуємо два значення - мінімальне значення, яке ми бачили досі, і максимальний прибуток, який ми могли б отримати лише за допомогою перших k елементів. Спочатку мінімальне значення, яке ми бачили до цього часу, є першим елементом, а максимальний прибуток - нульовим. Коли ми бачимо новий елемент, ми спочатку оновлюємо наш оптимальний прибуток, обчислюючи, скільки б ми заробили, купуючи за найнижчою ціною, яку бачили досі, і продаючи за поточною ціною. Якщо це краще, ніж оптимальне значення, яке ми нарахували до цього часу, то ми оновлюємо оптимальне рішення, щоб це була нова прибуток. Далі ми оновлюємо мінімальний елемент, який ви бачили до цього часу, як мінімум поточного найменшого елемента та нового елемента.

Оскільки на кожному кроці ми виконуємо лише роботу O (1) і відвідуємо кожен з n елементів рівно один раз, для цього потрібно O (n) часу! Більше того, він використовує лише допоміжні сховища O (1). Це так добре, як ми отримали досі!

Як приклад, на ваших входах, ось як цей алгоритм може працювати. Числа, що знаходяться між кожним зі значень масиву, відповідають значенню, проведеному алгоритмом у цій точці. Насправді ви б не зберігали все це (це займе O (n) пам'яті!), Але корисно побачити розвиток алгоритму:

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

Відповідь: (5, 10)

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

Відповідь: (4, 12)

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

Відповідь: (1, 5)

Чи можемо ми зараз зробити краще? На жаль, не в асимптотичному сенсі. Якщо ми використовуємо менше, ніж O (n) часу, ми не можемо роздивитися всі числа на великих входах і, таким чином, не можемо гарантувати, що ми не будемо пропускати оптимальну відповідь (ми можемо просто "заховати" її в елементах, які ми не дивився). Крім того, ми не можемо використовувати менше, ніж O (1) простір. Можливо, будуть оптимізовані постійні фактори, приховані в нотації big-O, але в іншому випадку ми не можемо очікувати, що ми знайдемо будь-які радикально кращі варіанти.

В цілому це означає, що у нас є такі алгоритми:

  • Наївні: O (n 2 ) час, O (1) простір.
  • Розділіть і переконайте: O (n lg n) час, O (lg n) простір.
  • Оптимізований поділ і перемагай: O (n) час, O (lg n) простір.
  • Динамічне програмування: O (n) час, O (1) простір.

Сподіваюся, це допомагає!

EDIT : Якщо вам цікаво, я зашифрував Python-версію цих чотирьох алгоритмів, щоб ви могли пограти з ними і судити про їх відносну ефективність. Ось код:

# 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. - Для обох рекурсивних дзвінків потрібен пробіл, але зазвичай ці дзвінки виконуються один за одним. Це означає, що компілятор може повторно використовувати пам'ять між дзвінками; як тільки один дзвінок повернеться, наступний дзвінок може повторно використовувати його простір. Як результат, вам потрібна лише пам'ять, щоб утримувати один виклик функції за раз, тому використання пам'яті пропорційно максимальній глибині стека викликів. Оскільки рекурсія закінчується на рівнях O (log n), потрібно використовувати лише O (log n) пам'ять. Чи прояснює це речі?
templatetypedef

Чи може хтось перенести їх до Рубі? Деякі рекурсії працюють не так, як у Python. Також ці рішення лише повертають максимальний прибуток; вони не повертають точки масиву, які приносили прибуток (який можна було б використати для повідомлення про відсоток збільшення прибутку за минулий час)
rcd

Концепція динамічного програмування насправді не потрібна для пояснення рішення про (n) час, але чудово, що ви пов'язані у всіх цих типах алгоритмів.
Rn222

Як ви можете побудувати на будь-якому з алгоритму суб O (n ^ 2), щоб знайти всі пари, відсортовані за прибутком?
ferk86

@templatetypedef Як би ми змінили підхід до динамічного програмування, якби ми почали з бюджету в розмірі M $, а замість одиничних запасів у нас були m запаси з цінами протягом n днів, як зазначено? тобто ми варіюємо кількість придбаних одиниць запасів, і дані про запаси доступні від 1 до н. акцій (наприклад, попередньо у нас було лише для Google, зараз у нас є і для 5 інших компаній)
Ronak Agrawal

32

Це проблема з максимальною сукупністю подачі з трохи непрямості. Проблема з максимальною сукупністю підсумків надається переліку цілих чисел, які можуть бути позитивними чи негативними, і знайти найбільшу суму суміжного підмножини цього списку.

Ви можете тривільно перетворити цю проблему на цю проблему, отримавши прибуток чи збиток між днями поспіль. Таким чином , ви б перетворити список цін на акції, наприклад , [5, 6, 7, 4, 2]в список доходів / збитків, наприклад, [1, 1, -3, -2]. Проблема суми підкріплення тоді досить легко вирішити: Знайдіть підряд з найбільшою сумою елементів у масиві


1
Я не думаю, що це цілком спрацьовує таким чином, оскільки якщо ви придбаєте запас в якийсь початковий день, ви не накопичуєте переваги дельти за попередній день. Або це не проблема в такому підході?
templatetypedef

1
@templatetypedef, тому ви відстежуєте найбільшу суму та поточну суму послідовностей. Коли поточна сума послідовностей опуститься нижче нуля, ви знаєте, що з цієї послідовності не заробляли б гроші і можете почати все заново. Відстежуючи найбільшу суму, ви автоматично знайдете найкращі дати купівлі / продажу.
MSN

6
@templatetypedef, до речі, ви робите те саме у своїй відповіді.
MSN

16

Я не дуже впевнений, чому це вважається питанням динамічного програмування. Це питання я бачив у підручниках та посібниках з алгоритмами, використовуючи час виконання O (n log n) та O (log n) для простору (наприклад, елементи інтерв'ю програмування). Це здається набагато простішою проблемою, ніж люди це роблять.

Це працює за відстеженням максимального прибутку, мінімальної ціни покупки, а отже, оптимальної ціни купівлі / продажу. Під час проходження кожного елемента в масиві він перевіряє, чи вказаний елемент менший, ніж мінімальна ціна покупки. Якщо він є, індекс мінімальної ціни покупки, (min ), оновлюється на індекс цього елемента. Крім того, для кожного елемента becomeABillionaireалгоритм перевіряє, чи arr[i] - arr[min](різниця між поточним елементом і мінімальною ціною придбання) більша за поточний прибуток. Якщо це так, прибуток оновлюється до тієї різниці, а покупка встановлюється, arr[min]а продажа встановлюється arr[i].

Працює в один прохід.

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

}

Співавтор: https://stackoverflow.com/users/599402/ephraim


2

Проблема ідентична максимальній підпослідовності.
Я її вирішив за допомогою динамічного програмування. Слідкуйте за поточним та попереднім (прибуток, дата придбання та продажу) Якщо поточний вище попереднього, замініть попередній на поточний.

    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

ось моє рішення 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, так, це рішення є правильним, але я б просив вас прочитати відповідь, запропоновану templatetypedef, оскільки у відповіді, наданій templatetypedef, згадуються всі можливі рішення, включаючи те, що розміщено Rohit. Рішення Рохіта - це фактично реалізація останнього рішення з O (n) з використанням динамічного програмування, згаданого у відповіді, наданій templatetypedef.
nits.kk

1
Припустимо, ваш масив є int A [] = {5, 4, 6, 7, 6, 3, 2, 5}; Тоді за вашою логікою ви купуватимете в індексі 6, а потім продаватимете його в індексі 3. Що неправильно. Ви не можете продати в минулому. Індекс продажу повинен бути більшим, ніж індекс купівлі.
developer747

1
Наведене вище рішення "майже" правильне. але він друкує абсолютний мінімум індексу замість індексу ціни "купити". Щоб виправити, вам потрібна інша змінна, скажімо, minBuyIndex, яку ви оновлюєте лише всередині блоку "if (profit> maxProfit)" та друкуйте її.
javabrew

1

Я придумав просте рішення - код швидше пояснює себе. Це одне з таких питань щодо динамічного програмування.

Код не бере участь у перевірці помилок та кращих випадках. Це просто зразок, який дає уявлення про базову логіку для вирішення проблеми.

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;

    }

Ось моє рішення. змінює алгоритм максимальної підрядкової послідовності. Розв’язує задачу в O (n). Я думаю, що це неможливо зробити швидше.


1

Це цікава проблема, адже це здається важким, але ретельна думка дає елегантне, вирівняне рішення.

Як зазначалося, це може бути вирішено грубою силою за O (N ^ 2) час. Для кожного запису в масиві (або списку) переглядайте всі попередні записи, щоб отримати мінімум чи макс, залежно від того, чи проблема полягає у пошуку найбільшого прибутку чи втрати.

Ось як продумати рішення в O (N): кожен запис представляє новий можливий макс (або хв). Тоді все, що нам потрібно зробити, це зберегти попередній хв (або макс) і порівняти різницю з поточним і попереднім хв (або макс). Простенька.

Ось код у Java як тест 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;
    }

}

У разі обчислення найбільшої втрати ми відстежуємо максимум у списку (ціна купівлі) аж до поточного запису. Потім обчислюємо різницю між максимальною та поточною записами. Якщо max - поточний> maxLoss, ми будемо зберігати це відмінності як новий maxLoss. Оскільки індекс максимуму гарантується меншим, ніж індекс поточного, ми гарантуємо, що дата "покупки" менша, ніж дата "продажу".

У разі обчислення найбільшого прибутку все перевертається. Ми відстежуємо хв у списку до поточного запису. Обчислюємо різницю між min та поточним записом (зворотний порядок у відніманні). Якщо поточний - min> maxGain, ми зберігаємо це відмінність як новий maxGain. Знову ж індекс 'купити' (хв) приходить перед показником поточного ('продати').

Нам потрібно лише слідкувати за maxGain (або maxLoss) та індексом min або max, але не обидва, і нам не потрібно порівнювати показники, щоб підтвердити, що "купувати" менше, ніж "продавати", оскільки ми зрозуміти це природним шляхом.


1

Максимальний прибуток від одного продажу, 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
}

Ось проект, який робить тестування складності часу на o (N) vs o (n ^ 2) підходи для випадкового набору даних на 100k ints. O (n ^ 2) займає 2 секунди, тоді як O (n) займає 0,01 с

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
}

Це більш повільний, o (n ^ 2) підхід, який петлі протягом решти днів на кожен день, подвійний цикл.


1

Відповідь у верхній частині голосування не допускає випадків, коли максимальний прибуток є негативним, і має бути змінений, щоб враховувати такі випадки. Це можна зробити, обмеживши діапазон циклу до (len (a) - 1) і змінивши спосіб визначення прибутку, змістивши індекс на одиницю.

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

Порівняйте цю версію функції з попередньою для масиву:

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

Можливість визначення максимального прибутку може бути відстеженням лівого мінімального та правого максимуму елементів у масиві при кожному індексі масиву. Коли ви перейдете до цін акцій, за будь-який день ви дізнаєтесь про найнижчу ціну до цього дня, а також дізнаєтесь максимальну ціну після (у тому числі) цього дня.

Наприклад, давайте визначимо а min_arrі max_arr, будучи заданим масивом arr. Індекс iв min_arrбуде мінімальним елементом arrдля всіх індексів <= i(зліва від i). Індекс iв max_arrбуде максимальним елементом arrдля всіх індексів >= i(право i включаючи i). Тоді ви можете знайти максимальну різницю між відповідними елементами у max_arrта `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

Це має працювати в O (n) час, але я вважаю, що він займає багато місця.


0

Це максимальна різниця двох елементів у масиві, і це моє рішення:

O (N) часова складність O (1) просторова складність

    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

Після того, як цього не вдалося скласти на іспиті з кодування в реальному часі на посаду інженера з рішень FB, я повинен був вирішити це в спокійній прохолодній атмосфері, тож ось мої 2 копійки:

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

Відповіді з коду відмовляються.
Притам Банерджі

0

Єдина відповідь, яка дійсно відповідає на питання, - це відповідь на @akash_magoon (і таким простим способом!), Але вона не повертає точний об'єкт, зазначений у питанні. Я трохи відреставрувався і отримав свою відповідь на PHP, повертаючи лише те, що задається:

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

Акуратне рішення:

+ (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 може повернути ціну покупки та ціну продажу, що дозволить максимізувати прибуток, обчислюючи складність часу O (n) та складність простору O (1) .


0

Ось моє рішення

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

Для всіх відповідей, що відстежують мінімальні та максимальні елементи, це рішення є насправді рішенням O (n ^ 2). Це тому, що наприкінці потрібно перевірити, максимум відбувся після мінімального чи ні. Якщо цього не сталося, потрібні подальші ітерації, поки ця умова не буде виконана, і це не призведе до найгіршого випадку O (n ^ 2). І якщо ви хочете пропустити додаткові ітерації, тоді потрібно набагато більше місця. У будь-якому випадку, ні-ні в порівнянні з рішенням динамічного програмування

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.