最大単一販売利益


123

ある日の株価を表すn個の整数の配列が与えられているとします。私たちは、ペア見つけたい(buyDay、sellDay)と、buyDay≤sellDay我々は上の株式を購入した場合、そのようなことを、buyDayとのそれを販売しsellDay、私たちは私たちの利益を最大にするが。

明らかに、可能なすべての(buyDay、sellDay)ペアを試し、それらのすべてから最良のものを取り出すことにより、アルゴリズムのO(n 2ソリューションがあります。しかし、O(n)時間で実行されるより良いアルゴリズムはありますか?


2
これは、間接レベルの最大合計サブシーケンス問題です。
MSN

2
@MSN:どうですか?彼は合計ではなく、要素間の違いに注目しています。
PengOne 2011

@ PengOne- True、しかしその質問は締め切られました。質問をわかりやすくするために書き直したので、これを開いたままにしておけますか?
templatetypedef

2
@PengOne、私が言ったように、それは間接の1つのレベルを持っています。具体的には、連続する一連の日における利益/損失の合計を最大化する必要があります。したがって、リストをゲイン/ロスに変換し、最大サブシーケンス合計を見つけます。
MSN、

1
@PDN:最小値が最大値の前に発生する可能性があるため、これは機能しません。(この場合)在庫を販売して、後で購入することはできません。
Ajeet Ganga

回答:


287

私はこの問題が大好きです。これは典型的なインタビューの質問であり、あなたの考え次第で、あなたはどんどん良い解決策を手に入れることになります。これをO(n 2)時間よりも優れた方法で実行することは確かに可能です。ここで問題について考えることができる3つの異なる方法を挙げました。うまくいけば、これはあなたの質問に答えます!

まず、分割統治ソリューション。入力を半分に分割し、各サブアレイの問題を解決してから、2つを組み合わせることでこれを解決できるかどうかを見てみましょう。実際にこれを実行でき、効率的に実行できることがわかりました。直感は以下の通りです。1日の場合、最良のオプションは、その日に購入してから、同じ日に売り戻して利益を得ないことです。それ以外の場合は、配列を2つに分割します。最適な答えが何であるかを考える場合、それは次の3つの場所のいずれかにある必要があります。

  1. 正しい売買ペアは完全に前半で発生します。
  2. 正しい売買ペアは完全に後半に発生します。
  3. 正しい売買ペアは両方の半分で発生します-前半で購入し、後半で販売します。

前半と後半でアルゴリズムを再帰的に呼び出すことで、(1)と(2)の値を取得できます。オプション(3)の場合、最高の利益を上げる方法は、前半の最低ポイントで購入し、後半の最高ポイントで販売することです。入力に対して単純な線形スキャンを実行して2つの値を見つけるだけで、2つの半分の最小値と最大値を見つけることができます。これにより、次の繰り返しのアルゴリズムが得られます。

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

マスター定理を使用して再帰を解決すると、これがO(n lg n)時間で実行され、再帰呼び出しにO(lg n)空間を使用することがわかります。単純なO(n 2)解法を打ち負かしました!

ちょっと待って!私たちはこれよりもずっと良いことができます。繰り返しにO(n)項がある唯一の理由は、各半分の最小値と最大値を見つけるために入力全体をスキャンする必要があることです。すでに各半分を再帰的に探索しているので、再帰によって各半分に格納されている最小値と最大値も返させることで、より良い結果が得られる可能性があります。言い換えると、私たちの再帰は次の3つを返します。

  1. 利益を最大化するための売買時間。
  2. 範囲全体での最小値。
  3. 範囲全体の最大値。

これらの最後の2つの値は、計算する再帰(1)と同時に実行できる単純な再帰を使用して再帰的に計算できます。

  1. 単一要素の範囲の最大値と最小値は、その要素にすぎません。
  2. 複数の要素の範囲の最大値と最小値は、入力を半分に分割し、各半分の最大値と最小値を見つけて、それぞれの最大値と最小値を取得することで確認できます。

このアプローチを使用すると、反復関係は次のようになります。

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

ここでマスター定理を使用すると、O(lg n)スペースでO(n)のランタイムが得られます。これは、元のソリューションよりもさらに優れています!

しかし、少し待ってください-私たちはこれよりもさらに良いことができます!動的プログラミングを使用してこの問題を解決することを考えてみましょう。問題は次のように考えることです。最初のk個の要素を調べたところ、問題の答えがわかっていると仮定します。最初の(k + 1)要素の問題を解決するために、最初のソリューションと組み合わせて(k + 1)番目の要素に関する知識を使用できますか?もしそうなら、最初のn個の要素について計算するまで、最初の要素、次に最初の2つ、次に最初の3つなどの問題を解決することにより、優れたアルゴリズムを実行できます。

これを行う方法について考えてみましょう。要素が1つしかない場合は、それが最良の売買ペアでなければならないことはすでにわかっています。ここで、最初のk要素の最良の答えがわかっていて、(k + 1)番目の要素を見てみます。次に、この値が最初のk要素よりも優れたソリューションを作成できる唯一の方法は、最初のk要素の最小値とその新しい要素の差が、これまでに計算した最大の差より大きい場合です。要素を移動するときに、これまでに見た最小値と、最初のk要素だけで得られる最大利益の2つの値を追跡するとします。最初は、これまでに見た最小値が最初の要素であり、最大利益はゼロです。新しい要素を見ると、まず、これまでに見た最低価格で購入し、現在の価格で販売することでどれだけ稼ぐかを計算して、最適利益を更新します。これがこれまでに計算した最適値よりも優れている場合は、この新しい利益になるように最適解を更新します。次に、これまでに確認した最小要素を、現在の最小要素と新しい要素の最小値に更新します。

各ステップで実行するのはO(1)の作業のみであり、n個の要素のそれぞれに正確に1回アクセスするだけなので、完了までに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)空間。

お役に立てれば!

編集:興味があれば、これら4つのアルゴリズムの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.-両方の再帰呼び出しにスペースが必要ですが、通常、これらの呼び出しは次々に実行されます。これは、コンパイラーが呼び出し間でメモリーを再利用できることを意味します。1つの呼び出しが戻ると、次の呼び出しはそのスペースを再利用できます。その結果、一度に1つの関数呼び出しを保持するのに必要なメモリは必要なだけなので、メモリ使用量は呼び出しスタックの最大深度に比例します。再帰はO(log n)レベルで終了するため、O(log n)メモリのみを使用する必要があります。それは物事を明確にしますか?
templatetypedef

誰かがこれらをRubyに移植できますか?一部の再帰は、Pythonと同じようには機能しません。また、これらのソリューションは最大の利益のみを返します。それらは、利益をもたらした配列ポイントを返しません(これは、過去の利益増加の割合を報告するために使用できます)
rcd

動的プログラミングの概念は、O(n)時間の解法を説明するために実際には必要ありませんが、これらのタイプのアルゴリズムをすべて結び付けるのは素晴らしいことです。
Rn222 2012年

どのようにサブO(n ^ 2)アルゴリズムを構築して、利益でソートされたすべてのペアを見つけることができるでしょうか
ferk86

@templatetypedef M $の予算で開始する場合、ダイナミックプログラミングアプローチをどのように変更し、単一の株式ではなく、n日間の価格でm株を指定した場合、どのように変更しますか?つまり、購入した株数と1株からn株まで利用可能な株数のデータを変更します(以前のように、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)をスペースに使用する教科書とアルゴリズムガイドで見ました(例:Elements of Programming Interviews)。人々が考えているよりもずっと単純な問題のようです。

これは、最大利益、最小購入価格、したがって最適な購入/販売価格を追跡することによって機能します。配列の各要素を通過するときに、指定された要素が最小購入価格よりも小さいかどうかを確認します。そうである場合、最小購入価格インデックス(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 2015

1
配列がint A [] = {5、4、6、7、6、3、2、5}であるとします。次に、ロジックに従って、インデックス6で購入し、インデックス3で販売します。これは誤りです。過去に売ることはできません。売り指数は買い指数より大きくなければなりません。
developer747

1
上記のソリューションは「ほぼ」正しいです。ただし、「購入」価格のインデックスではなく、絶対最小インデックスを出力します。修正するには、別の変数、たとえば「if(profit> maxProfit)」ブロック内でのみ更新するminBuyIndexが必要であり、それを出力します。
javabrew

1

私は簡単な解決策を考え出しました-コードは自明です。これは、動的プログラミングの質問の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)の解について考える方法は次のとおりです。各エントリは、新しい可能な最大(または最小)を表します。次に、前の最小値(または最大値)を保存し、差分を現在および前の最小値(または最大値)と比較するだけです。かんたん。

以下は、JUnitテストとしてのJavaのコードです。

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として保持します。maxのインデックスは現在のインデックスよりも小さいことが保証されているため、「購入」日が「販売」日よりも短いことが保証されています。

最大ゲインを計算する場合、すべてが逆になります。現在のエントリまでのリストの分を追跡します。最小値と現在のエントリの差を計算します(減算の順序を逆にします)。current-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
}

これは、100k整数のランダムデータセットに対してo(N)とo(n ^ 2)のアプローチの時間の複雑さのテストを行うプロジェクトです。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)に制限し、インデックスを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とを定義してみましょう。インデックス入力は、すべてのインデックス(左およびiを含む)の最小要素です。インデックス入力は、すべてのインデックス(iの権利を含み、iを含む)の最大要素です。次に、と `min_arr 'の対応する要素間の最大の差を見つけることができます。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

これはO(n)時間で実行されるはずですが、多くのスペースを消費すると思います。


0

これは配列内の2つの要素間の最大の違いであり、これが私の解決策です。

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 2018年

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)の最悪のケースが残ります。また、余分な反復をスキップする場合は、さらに多くのスペースが必要になります。いずれにせよ、動的プログラミングソリューションと比較してノーノー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.