最大单笔销售利润


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

Answers:


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. 通过将输入分成两半,找到每个半的最大值和最小值,然后取各自的最大值和最小值,可以找到多个元素范围的最大值和最小值。

如果我们使用这种方法,那么我们的递归关系就是

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)个元素。那么,此值可以创建比我们对前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)空间。

希望这可以帮助!

编辑:如果您有兴趣,我已经为这四种算法编写了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

谁能将这些移植到Ruby?某些递归的工作方式与Python中不同。同样,这些解决方案仅返回最大的利润。他们不返回产生利润的数组点(可用于报告过去利润增长的百分比)
rcd 2012年

解释O(n)时间解决方案并不是真正需要动态编程的概念,但是将所有这些类型的算法都结合使用是一件很棒的事情。
Rn222 2012年

您如何建立任何子O(n ^ 2)算法来查找按利润排序的所有货币对?
ferk86

@templatetypedef如果我们以M $的预算开始,我们将如何改变动态规划方法,而不是单只股票,而是有m只股票的给定价格超过n天?也就是说,我们会改变购买的股票单位数量,并将可用的股票数据从1种股票转换为n种股票(就像以前一样,我们只有Google,现在也有5家其他公司)
Ronak Agrawal,2015年

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发布的解决方案。Rohit的解决方案实际上是使用templatetypedef提供的答案中提到的动态编程对O(n)的最后一个解决方案的实现。
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-current> maxLoss,则我们将此差异保留为新的maxLoss。由于保证最大指数小于当前指数,因此我们保证“购买”日期小于“卖出”日期。

在计算最大增益的情况下,一切都将相反。我们跟踪列表中的分钟,直到当前条目为止。我们计算最小值和当前条目之间的差异(颠倒减法的顺序)。如果current-min> maxGain,那么我们将此差异保留为新的maxGain。同样,“买”(min)的索引位于当前(“卖”)的索引之前。

我们只需要跟踪maxGain(或maxLoss)以及min或max的索引,但不能同时跟踪两者,并且我们不需要比较索引以验证“ buy”小于“ sell”,因为自然地得到这个。


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
}

这是一个对100k个整数上的随机数据集进行o(N)vs o(n ^ 2)方法的时间复杂度测试的项目。O(n ^ 2)需要2秒,而O(n)需要0.01s

https://github.com/gulakov/complexity.js

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

这是一种较慢的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_arrand max_arr,给定数组为arr。index iin min_arrarr所有索引中最小的元素<= i(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);

不鼓励仅使用代码的答案。
Pritam Banerjee

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)的最坏情况。而且,如果您想跳过多余的迭代,则需要更多空间。无论哪种方式,与动态编程解决方案相比都是不可行的

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.