Keuntungan penjualan tunggal maksimum


123

Misalkan kita diberi array n bilangan bulat yang mewakili harga saham dalam satu hari. Kita ingin mencari pair (buyDay, sellDay) , dengan buyDay ≤ sellDay , sehingga jika kita membeli saham pada buyDay dan menjualnya pada sellDay , kita akan memaksimalkan keuntungan kita.

Jelas ada solusi O (n 2 ) untuk algoritme dengan mencoba semua kemungkinan pasangan (buyDay, sellDay) dan mengambil yang terbaik dari semuanya. Namun, apakah ada algoritma yang lebih baik, mungkin yang berjalan dalam waktu O (n) ?


2
Ini adalah masalah penjumlahan maksimum dengan tingkat tipuan.
MSN

2
@ MSN: Bagaimana? Dia tidak melihat jumlah sama sekali, melainkan perbedaan antar elemen.
PengOne

@ PengOne- Benar, tapi pertanyaan itu sudah ditutup. Saya menyusun ulang pertanyaan itu agar lebih mudah dipahami, jadi bisakah kita mencoba membiarkan yang ini tetap terbuka?
templatetypedef

2
@PengOne, Seperti yang saya katakan, ini memiliki satu tingkat tipuan. Secara khusus, Anda ingin memaksimalkan jumlah keuntungan / kerugian selama beberapa hari yang berdekatan. Oleh karena itu, konversikan daftar tersebut menjadi keuntungan / kerugian dan temukan jumlah maksimum selanjutnya.
MSN

1
@PDN: Itu tidak akan berfungsi karena min dapat terjadi sebelum maks. Anda tidak bisa menjual saham (dalam hal ini), dan membelinya nanti.
Ajeet Ganga

Jawaban:


287

Saya suka masalah ini. Ini adalah pertanyaan wawancara klasik dan tergantung bagaimana Anda memikirkannya, Anda akan mendapatkan solusi yang lebih baik dan lebih baik. Tentu saja mungkin untuk melakukan ini lebih baik daripada waktu O (n 2 ), dan saya telah membuat daftar tiga cara berbeda yang dapat Anda pikirkan tentang masalah ini di sini. Semoga ini menjawab pertanyaan Anda!

Pertama, solusi bagi-dan-taklukkan. Mari kita lihat apakah kita bisa menyelesaikan ini dengan membagi input menjadi dua, menyelesaikan masalah di setiap sublarik, lalu menggabungkan keduanya. Ternyata kita sebenarnya bisa melakukan ini, dan bisa melakukannya dengan efisien! Intuisi adalah sebagai berikut. Jika kita memiliki satu hari, pilihan terbaik adalah membeli pada hari itu dan kemudian menjualnya kembali pada hari yang sama tanpa keuntungan. Jika tidak, pisahkan array menjadi dua bagian. Jika kita berpikir tentang apa jawaban yang optimal, itu harus ada di salah satu dari tiga tempat:

  1. Pasangan beli / jual yang benar terjadi sepenuhnya dalam paruh pertama.
  2. Pasangan beli / jual yang benar terjadi sepenuhnya dalam paruh kedua.
  3. Pasangan beli / jual yang benar terjadi di kedua bagian - kita membeli di paruh pertama, lalu menjual di paruh kedua.

Kita bisa mendapatkan nilai untuk (1) dan (2) dengan menggunakan algoritma kita secara rekursif pada bagian pertama dan kedua. Untuk opsi (3), cara mendapatkan keuntungan tertinggi adalah membeli di titik terendah di paruh pertama dan menjual di titik terbesar di paruh kedua. Kita dapat menemukan nilai minimum dan maksimum dalam dua bagian hanya dengan melakukan pemindaian linier sederhana pada input dan menemukan dua nilai. Ini kemudian memberi kita algoritme dengan pengulangan berikut:

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

Menggunakan Teorema Master untuk menyelesaikan pengulangan, kita menemukan bahwa ini berjalan dalam waktu O (n lg n) dan akan menggunakan ruang O (lg n) untuk panggilan rekursif. Kami baru saja mengalahkan solusi O (n 2 ) yang naif !

Tapi tunggu! Kami bisa melakukan jauh lebih baik dari ini. Perhatikan bahwa satu-satunya alasan kita memiliki suku O (n) dalam pengulangan kita adalah karena kita harus memindai seluruh input mencoba menemukan nilai minimum dan maksimum di setiap setengahnya. Karena kita sudah menjelajahi setiap bagian secara rekursif, mungkin kita dapat melakukan lebih baik dengan meminta rekursi juga mengembalikan nilai minimum dan maksimum yang disimpan di setiap bagian! Dengan kata lain, rekursi kami mengembalikan tiga hal:

  1. Waktu beli dan jual untuk memaksimalkan keuntungan.
  2. Nilai minimum keseluruhan dalam rentang tersebut.
  3. Nilai maksimum secara keseluruhan dalam rentang tersebut.

Dua nilai terakhir ini dapat dihitung secara rekursif menggunakan rekursi langsung yang dapat kita jalankan bersamaan dengan rekursi untuk menghitung (1):

  1. Nilai maks dan min dari rentang elemen tunggal hanyalah elemen itu.
  2. Nilai maks dan min dari beberapa rentang elemen dapat ditemukan dengan membagi input menjadi dua, mencari nilai maks dan min dari setiap setengah, lalu mengambil nilai maks dan min masing-masing.

Jika kita menggunakan pendekatan ini, relasi pengulangan kita sekarang

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

Menggunakan Teorema Utama di sini memberi kita runtime dari O (n) dengan ruang O (lg n), yang bahkan lebih baik daripada solusi awal kita!

Tapi tunggu sebentar - kita bisa melakukan lebih baik dari ini! Mari pikirkan untuk memecahkan masalah ini menggunakan pemrograman dinamis. Idenya adalah memikirkan masalah sebagai berikut. Misalkan kita mengetahui jawaban soal setelah melihat elemen k pertama. Bisakah kita menggunakan pengetahuan kita tentang elemen (k + 1) st, dikombinasikan dengan solusi awal kita, untuk menyelesaikan masalah elemen pertama (k + 1)? Jika demikian, kita bisa mendapatkan algoritma yang bagus dengan menyelesaikan masalah untuk elemen pertama, lalu dua yang pertama, lalu tiga yang pertama, dll. Sampai kita menghitungnya untuk n elemen pertama.

Mari kita pikirkan bagaimana melakukan ini. Jika kita hanya memiliki satu elemen, kita sudah tahu bahwa itu pasti pasangan beli / jual terbaik. Sekarang misalkan kita mengetahui jawaban terbaik untuk elemen k pertama dan melihat elemen (k + 1) st. Maka satu-satunya cara agar nilai ini dapat menciptakan solusi yang lebih baik daripada yang kita miliki untuk elemen k pertama adalah jika perbedaan antara elemen k terkecil dan elemen baru lebih besar daripada perbedaan terbesar yang telah kita hitung sejauh ini. Jadi misalkan saat kita melintasi elemen, kita melacak dua nilai - nilai minimum yang kita lihat sejauh ini, dan keuntungan maksimum yang bisa kita hasilkan hanya dengan k elemen pertama. Awalnya, nilai minimum yang kami lihat sejauh ini adalah elemen pertama, dan keuntungan maksimum adalah nol. Saat kita melihat elemen baru, pertama-tama kami memperbarui laba optimal kami dengan menghitung berapa banyak yang kami hasilkan dengan membeli pada harga terendah yang terlihat sejauh ini dan menjual dengan harga saat ini. Jika ini lebih baik dari nilai optimal yang telah kami hitung sejauh ini, kami memperbarui solusi optimal menjadi keuntungan baru ini. Selanjutnya, kami memperbarui elemen minimum yang terlihat sejauh ini menjadi minimum elemen terkecil saat ini dan elemen baru.

Karena pada setiap langkah kita hanya melakukan O (1) pekerjaan dan kita mengunjungi masing-masing n elemen tepat satu kali, ini membutuhkan waktu O (n) untuk diselesaikan! Selain itu, hanya menggunakan penyimpanan tambahan O (1). Sejauh ini sudah bagus!

Sebagai contoh, pada masukan Anda, berikut ini bagaimana algoritma ini dapat berjalan. Angka-angka di antara masing-masing nilai larik sesuai dengan nilai yang dipegang oleh algoritme pada saat itu. Anda tidak akan benar-benar menyimpan semua ini (ini akan membutuhkan O (n) memori!), Tetapi sangat membantu untuk melihat algoritme berkembang:

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

Jawaban: (5, 10)

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

Jawaban: (4, 12)

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

Jawaban: (1, 5)

Bisakah kita melakukannya lebih baik sekarang? Sayangnya, tidak dalam arti asimtotik. Jika kita menggunakan waktu kurang dari O (n), kita tidak dapat melihat semua angka pada input yang besar dan dengan demikian tidak dapat menjamin bahwa kita tidak akan melewatkan jawaban yang optimal (kita hanya dapat "menyembunyikan" di elemen yang kita gunakan. tidak melihat). Selain itu, kami tidak dapat menggunakan kurang dari O (1) spasi. Mungkin ada beberapa pengoptimalan pada faktor konstan yang tersembunyi dalam notasi O besar, tetapi sebaliknya kita tidak dapat berharap untuk menemukan opsi yang jauh lebih baik.

Secara keseluruhan, ini berarti kami memiliki algoritme berikut:

  • Naif: O (n 2 ) waktu, O (1) ruang.
  • Bagilah-dan-Taklukkan: O (n lg n) waktu, O (lg n) ruang.
  • Divide-and-Conquer yang Dioptimalkan: O (n) waktu, O (lg n) ruang.
  • Pemrograman dinamis: O (n) waktu, O (1) ruang.

Semoga ini membantu!

EDIT : Jika Anda tertarik, saya telah membuat kode versi Python dari empat algoritme ini sehingga Anda dapat bermain-main dengannya dan menilai kinerja relatifnya. Berikut kodenya:

# 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.- Spasi diperlukan untuk kedua panggilan rekursif, tetapi biasanya panggilan tersebut dilakukan satu demi satu. Ini berarti bahwa kompilator dapat menggunakan kembali memori antar panggilan; setelah satu panggilan kembali, panggilan berikutnya dapat menggunakan kembali ruangnya. Akibatnya, Anda hanya perlu memori untuk menahan satu panggilan fungsi pada satu waktu, sehingga penggunaan memori sebanding dengan kedalaman maksimum tumpukan panggilan. Karena rekursi berakhir pada level O (log n), hanya memori O (log n) yang perlu digunakan. Apakah itu menjelaskan sesuatu?
templatetypedef

Adakah yang bisa memindahkan ini ke Ruby? Beberapa rekursi tidak bekerja dengan cara yang sama seperti di Python. Juga solusi ini hanya mengembalikan keuntungan maksimum; mereka tidak mengembalikan poin array yang menghasilkan keuntungan (yang dapat digunakan untuk melaporkan persentase peningkatan keuntungan di masa lalu)
rcd

Konsep pemrograman dinamis tidak terlalu diperlukan untuk menjelaskan solusi waktu O (n), tetapi sangat bagus jika Anda mengikat semua jenis algoritme ini.
Rn222

Bagaimana Anda bisa membangun algoritma sub O (n ^ 2) untuk menemukan semua pasangan yang diurutkan berdasarkan keuntungan?
ferk86

@templatetypedef bagaimana kita akan mengubah pendekatan pemrograman dinamis jika kita mulai dengan anggaran M $ dan alih-alih satu saham kita memiliki m saham dengan harga selama n hari seperti yang diberikan? yaitu kami memvariasikan jumlah unit stok yang dibeli dan data stok tersedia dari 1 saham menjadi n stok (seperti sebelumnya, kami hanya memiliki untuk Google, sekarang kami memiliki untuk 5 perusahaan lain juga)
Ronak Agrawal

32

Ini adalah masalah urutan jumlah maksimum dengan sedikit tipuan. Jumlah maksimum masalah selanjutnya diberikan daftar bilangan bulat yang bisa positif atau negatif, temukan jumlah terbesar dari subset yang berdekatan dari daftar itu.

Anda dapat dengan mudah mengubah masalah ini menjadi masalah itu dengan mengambil untung atau rugi antara hari-hari berturut-turut. Jadi, Anda akan mengubah daftar harga saham, misalnya [5, 6, 7, 4, 2]menjadi daftar keuntungan / kerugian, misalnya [1, 1, -3, -2]. Masalah penjumlahan selanjutnya cukup mudah dipecahkan: Temukan urutan dengan jumlah elemen terbesar dalam sebuah array


1
Menurut saya cara ini tidak cukup berhasil, karena jika Anda membeli saham pada suatu hari pertama, Anda tidak mendapatkan keuntungan dari delta dari hari sebelumnya. Atau apakah ini bukan masalah dalam pendekatan ini?
templatetypedef

1
@templatetypedef, itulah alasan Anda melacak jumlah terbesar dan jumlah urutan saat ini. Ketika jumlah urutan saat ini berada di bawah nol, Anda tahu bahwa Anda tidak akan menghasilkan uang dengan urutan itu dan Anda dapat memulai dari awal lagi. Dengan melacak jumlah terbesar, Anda secara otomatis akan menemukan tanggal beli / jual terbaik.
MSN

6
@templatetypedef, kebetulan, Anda melakukan hal yang sama dalam jawaban Anda.
MSN

16

Saya tidak begitu yakin mengapa ini dianggap sebagai pertanyaan pemrograman dinamis. Saya telah melihat pertanyaan ini di buku teks dan panduan algoritma menggunakan runtime O (n log n) dan O (log n) untuk ruang (misalnya, Elemen Wawancara Pemrograman). Sepertinya masalah yang jauh lebih sederhana daripada yang orang bayangkan.

Ini bekerja dengan melacak keuntungan maksimal, harga beli minimum, dan akibatnya, harga beli / jual yang optimal. Saat melewati setiap elemen dalam larik, ia memeriksa untuk melihat apakah elemen yang diberikan lebih kecil dari harga beli minimum. Jika ya, indeks harga beli minimum, ( min), diperbarui menjadi indeks elemen tersebut. Selain itu, untuk setiap elemen, becomeABillionairealgoritme memeriksa apakah arr[i] - arr[min](perbedaan antara elemen saat ini dan harga beli minimum) lebih besar dari keuntungan saat ini. Jika ya, keuntungan diperbarui menjadi selisih itu dan beli ditetapkan ke arr[min]dan jual ditetapkan ke arr[i].

Berjalan dalam sekali jalan.

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

}

Penulis bersama: https://stackoverflow.com/users/599402/ephraim


2

Masalahnya identik dengan sub-urutan maksimum yang
saya selesaikan menggunakan pemrograman Dinamis. Melacak saat ini dan sebelumnya (Laba, tanggal beli & tanggal jual) Jika saat ini lebih tinggi dari sebelumnya maka ganti sebelumnya dengan saat ini.

    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

inilah solusi My 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, ya solusi ini benar tetapi saya akan meminta Anda untuk membaca jawaban yang diberikan oleh templatetypedef, seperti dalam jawaban yang diberikan oleh templatetypedef, semua solusi yang mungkin disebutkan termasuk yang diposting oleh Rohit. Solusi Rohit sebenarnya merupakan implementasi dari solusi terakhir dengan O (n) menggunakan program dinamis yang disebutkan dalam jawaban yang disediakan oleh templatetypedef.
nits.kk

1
Misalkan array Anda adalah int A [] = {5, 4, 6, 7, 6, 3, 2, 5}; Lalu menurut logika anda akan membeli di indeks 6 lalu menjualnya di indeks 3. Mana yang salah. Anda tidak bisa menjual di masa lalu. Indeks jual harus lebih besar dari indeks beli.
developer747

1
Solusi di atas "hampir" benar. tetapi mencetak indeks min absolut, bukan indeks harga "beli". Untuk mengoreksi, Anda memerlukan variabel lain, katakan minBuyIndex yang Anda perbarui hanya di dalam blok "if (profit> maxProfit)" dan cetak itu.
javabrew

1

Saya telah menemukan solusi sederhana - kode lebih dari cukup jelas. Ini adalah salah satu pertanyaan pemrograman dinamis.

Kode tidak menangani pemeriksaan kesalahan dan kasus tepi. Ini hanya contoh untuk memberikan gambaran tentang logika dasar untuk menyelesaikan masalah.

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;

    }

Inilah solusi saya. memodifikasi algoritma sub-urutan maksimum. Memecahkan masalah di O (n). Saya pikir itu tidak bisa dilakukan lebih cepat.


1

Ini adalah masalah yang menarik, karena tampaknya sulit, tetapi pemikiran yang cermat menghasilkan solusi yang elegan dan sederhana.

Seperti yang telah dicatat, kekerasan dapat diselesaikan dalam waktu O (N ^ 2). Untuk setiap entri dalam larik (atau daftar), ulangi semua entri sebelumnya untuk mendapatkan nilai minimum atau maksimum tergantung pada apakah masalahnya adalah menemukan untung atau rugi terbesar.

Berikut adalah cara memikirkan solusi dalam O (N): setiap entri mewakili kemungkinan maks (atau min) baru. Kemudian, yang perlu kita lakukan adalah menyimpan min sebelumnya (atau maks), dan membandingkan perbedaannya dengan arus dan min sebelumnya (atau maks). Sangat mudah.

Ini kodenya, di Java sebagai pengujian 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;
    }

}

Dalam kasus penghitungan kerugian terbesar, kami melacak nilai maksimum dalam daftar (harga beli) hingga entri saat ini. Kami kemudian menghitung perbedaan antara max dan entri saat ini. Jika max - current> maxLoss, maka kami tetap menggunakan diff sebagai maxLoss baru. Karena indeks maks dijamin kurang dari indeks saat ini, kami menjamin bahwa tanggal 'beli' kurang dari tanggal 'jual'.

Dalam kasus menghitung keuntungan terbesar, semuanya terbalik. Kami melacak min dalam daftar hingga entri saat ini. Kami menghitung perbedaan antara min dan entri saat ini (membalik urutan pengurangan). Jika saat ini - min> maxGain, maka kami menyimpan perbedaan ini sebagai maxGain baru. Sekali lagi, indeks 'beli' (min) datang sebelum indeks saat ini ('jual').

Kita hanya perlu melacak maxGain (atau maxLoss), dan indeks min atau max, tapi tidak keduanya, dan kita tidak perlu membandingkan indeks untuk memvalidasi bahwa 'beli' kurang dari 'jual', karena kita dapatkan ini secara alami.


1

Keuntungan penjualan tunggal maksimum, solusi 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
}

Berikut adalah proyek yang melakukan pengujian kompleksitas waktu pada pendekatan o (N) vs o (n ^ 2) pada kumpulan data acak pada 100k int. O (n ^ 2) membutuhkan waktu 2 detik, sedangkan O (n) membutuhkan 0,01 detik

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
}

Ini adalah pendekatan yang lebih lambat, o (n ^ 2) yang berulang melalui sisa hari untuk setiap hari, putaran ganda.


1

Jawaban pilihan teratas tidak memungkinkan untuk kasus di mana keuntungan maksimum negatif dan harus dimodifikasi untuk memungkinkan kasus seperti itu. Seseorang dapat melakukannya dengan membatasi kisaran loop ke (len (a) - 1) dan mengubah cara penentuan keuntungan dengan menggeser indeks satu per satu.

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

Bandingkan versi fungsi ini dengan yang sebelumnya untuk array:

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

singSellProfit(s)
-1

DynamicProgrammingSingleSellProfit(s)
0

0
static void findmaxprofit(int[] stockvalues){
    int buy=0,sell=0,buyingpoint=0,sellingpoint=0,profit=0,currentprofit=0;
    int finalbuy=0,finalsell=0;
    if(stockvalues.length!=0){
        buy=stockvalues[0];
    }           
    for(int i=1;i<stockvalues.length;i++){  
        if(stockvalues[i]<buy&&i!=stockvalues.length-1){                
            buy=stockvalues[i];
            buyingpoint=i;
        }               
        else if(stockvalues[i]>buy){                
            sell=stockvalues[i];
            sellingpoint=i;
        }
        currentprofit=sell-buy;         
        if(profit<currentprofit&&sellingpoint>buyingpoint){             
            finalbuy=buy;
            finalsell=sell;
            profit=currentprofit;
        }

    }
    if(profit>0)
    System.out.println("Buy shares at "+finalbuy+" INR and Sell Shares "+finalsell+" INR and Profit of "+profit+" INR");
    else
        System.out.println("Don't do Share transacations today");
}

0

Kemungkinan untuk menentukan keuntungan maksimum mungkin dengan melacak elemen minimum sisi kiri dan maksimum sisi kanan dalam larik pada setiap indeks dalam larik. Ketika Anda kemudian mengulangi harga saham, untuk hari tertentu Anda akan mengetahui harga terendah hingga hari itu, dan Anda juga akan mengetahui harga maksimum setelah (dan termasuk) hari itu.

Sebagai contoh, mari kita definisikan a min_arrdan max_arr, dengan array yang diberikan arr. Indeks idalam min_arrakan menjadi elemen minimum dalam arruntuk semua indeks <= i(kiri dan termasuk i). Indeks imasuk max_arrakan menjadi elemen maksimum dalam arruntuk semua indeks >= i(hak dari dan termasuk i). Kemudian, Anda dapat menemukan perbedaan maksimum antara elemen terkait dalam max_arrdan `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

Ini harus berjalan dalam waktu O (n), tapi saya yakin ini menghabiskan banyak ruang.


0

Ini adalah perbedaan maksimum antara dua elemen dalam array dan ini adalah solusi saya:

O (N) kompleksitas waktu O (1) kompleksitas ruang

    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

Setelah gagal dalam ujian pengkodean langsung untuk posisi insinyur solusi FB, saya harus menyelesaikannya dalam suasana sejuk yang tenang, jadi inilah 2 sen saya:

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

Kode hanya jawaban tidak dianjurkan.
Pritam Banerjee

0

Satu-satunya jawaban yang benar-benar menjawab pertanyaan adalah salah satu dari @akash_magoon (dan dengan cara yang sangat sederhana!), Tetapi tidak mengembalikan objek persis seperti yang ditentukan dalam pertanyaan. Saya merefaktor sedikit dan mendapatkan jawaban saya di PHP mengembalikan apa yang ditanyakan:

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

Solusi yang rapi:

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

Program pada python3 ini dapat mengembalikan harga beli dan harga jual yang akan memaksimalkan keuntungan, dihitung dengan kompleksitas waktu O (n) dan kompleksitas ruang O (1) .


0

Inilah solusi saya

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

Untuk semua jawaban yang mencatat elemen minimum dan maksimum, solusi itu sebenarnya adalah solusi O (n ^ 2). Ini karena pada akhirnya harus diperiksa apakah maksimum terjadi setelah minimum atau tidak. Jika tidak, iterasi lebih lanjut diperlukan sampai kondisi tersebut terpenuhi, dan ini meninggalkan kasus terburuk O (n ^ 2). Dan jika Anda ingin melewatkan iterasi ekstra maka diperlukan lebih banyak ruang. Either way, tidak-tidak dibandingkan dengan solusi pemrograman dinamis

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.