LeetCode & 劍指Offer:一文詳解六道股票問題(買賣股票的最佳時機)--動態規劃

介紹:

股票問題一共有六道:買賣股票的最佳時機(IIIIIIIV)、含冷凍期含手續費。本次解析還包括一道面試題:面試題63. 股票的最大利潤(即買賣股票的最佳時機I)

股票問題的方法就是 動態規劃,因爲它包含了重疊子問題,即買賣股票的最佳時機是由之前買或不買的狀態決定的,而之前買或不買又由更早的狀態決定的…

LeetCode 121 :買賣股票的最佳時機I

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多隻允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。

注意:你不能在買入股票前賣出股票。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5

解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。

示例 2:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

解題思路:動態規劃

動態規劃一般分爲一維、二維、多維(使用狀態壓縮),對應形式爲 dp(i)、dp(i)(j)、二進制dp(i)(j)。

  1. 動態規劃做題步驟:
  • 明確 dp(i) 應該表示什麼(二維情況:dp(i)(j));
  • 根據 dp(i) 和 dp(i-1)的關係得出狀態轉移方程;
  • 確定初始條件,如 dp(0)。

2. 本題思路

由於,本題這裏最多隻允許完成一筆交易(即買入和賣出一支股票一次),即一維動態規劃思想。用dp[i]表示前 i天的最大利潤,因爲我們始終要使利潤最大化,則:

dp[i] = max(dp[i-1], prices[i]-minprice)

代碼實現:

  • C++:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int minprice = int(1e9);
        int maxprofit = 0;
        // auto 關鍵字,price取遍prices中的元素
        for (auto price : prices){
            maxprofit = max(maxprofit, price - minprice);
            minprice = min(minprice, price);
        }
        return maxprofit;
    }
};

  • Python
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if (len(prices)==0):
            return 0;
        maxprofit,minprice = [0]*len(prices),prices[0]

        for i in range(1,len(prices)):
            maxprofit[i] = max(maxprofit[i-1],prices[i]-minprice)
            minprice = min(prices[i],minprice)

        return maxprofit[-1]

LeetCode:122. 買賣股票的最佳時機 II

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。 對於交易次數沒有限制

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7

解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。

隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4

解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。

注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

提示:

1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4

解題思路:由於對於交易次數沒有限制,可以每在前一個時刻比下一個時刻價格要小的情況下就進行一次交易,這樣總的利潤是最大的。如下圖所示:

image

從上圖中,我們可以觀察到 A+B+CA+B+C 的和等於差值 DD 所對應的連續峯和谷的高度之差。

代碼實現:

  • C++:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        int maxProfit = 0;
        for(int i=1;i<length;i++){
            if(prices[i-1]<prices[i]){
                maxProfit+=prices[i]-prices[i-1];
            }
        }
        return maxProfit;
    }
};

  • Python:
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        maxProfit = 0
        for i in range(1,len(prices)):
            if(prices[i-1]<prices[i]):
                maxProfit = maxProfit + prices[i] - prices[i-1]
        return maxProfit

LeetCode:123. 買賣股票的最佳時機 III

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7

解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。

隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4

解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。

注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1] 輸出: 0 解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

解題思路:

  1. 典型的動態規劃解法—三維數組的解法:

如果我們把買+賣算一次交易,那麼這道題的限制是交易數,天數,手上有沒有股票(有股票就不能買,沒有股票就不能賣)

首先定義一個狀態方程,建立一個三維的數組:dp。

  • dp[i][j][0] 今天是第i天,進行j次交易了,手上沒有股票,
  • dp[i][j][1] 今天是第i天,進行j次交易,手上有股票。
  • dp是求無論手上有沒有股票,最後的最大利潤。所以最後如果股票全都賣出去了,肯定能獲得最大利潤啦。即dp[-1][j][0]。

狀態轉移:

  • dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
  • dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])

dp[i][j][0]表示當前手上沒有股票,這樣的狀態可以由兩個狀態轉移得來:前一天沒有股票,即dp[i-1][j][0]和前一天手上有股票,但賣出去了,即dp[i-1][j][1] + prices[i]這裏需要+ prices[i]因爲賣出股票會有收益。

同理,dp[i][j][1] 可以由:dp[i-1][j][1]和前一天沒有股票但前一天買入了,即dp[i-1][j-1][0] - prices[i]轉移。

  • Python代碼如下:
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 2筆
        # 定義賣出股票,交易次數 j + 1
        #dp[i][j][0] 今天是第i天 進行 j次 交易 手上沒有股票
        #dp[i][j][1] 今天是第i天 進行 j次 交易 手上有股票
        if not prices: return 0
        n = len(prices)
        dp = [[[0] * 2 for _ in range(3)] for _ in range(n)]
        for k in range(3): #base case i=0
            dp[0][k][0] = 0
            dp[0][k][1] = -prices[0]
        for i in range(1, n):
            for j in range(3):
                if not j:
                    dp[i][j][0] = dp[i-1][j][0]  # 0 base case j=0
                else:
                    dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j-1][1] + prices[i]) 
                dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j][0] - prices[i])   
        return max(dp[-1][0][0], dp[-1][1][0], dp[-1][2][0])

轉換成一維數組形式:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices)<1:
            return 0
        if len(prices)//2 <= 2:
            maxProfit = 0
            for i in range(1,len(prices)):
                if(prices[i-1]<prices[i]):
                    maxProfit+=prices[i]-prices[i-1]
            return maxProfit

        dp = [0]*(3)
        v = [prices[0]]*(3)
        for i in range(1,len(prices)):
            for t in range(1,3):
                v[t] = min(v[t], prices[i] - dp[t - 1])
                dp[t] = max(dp[t], prices[i] - v[t])

        return dp[2]

LeetCode 188. 買賣股票的最佳時機 IV

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 k 筆交易。

**注意: **你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [2,4,1], k = 2
輸出: 2

解釋: 在第 1 天 (股票價格 = 2) 的時候買入,在第 2 天 (股票價格 = 4) 的時候賣出,這筆交易所能獲得利潤 = 4-2 = 2 。

示例 2:

輸入: [3,2,6,5,0,3], k = 2
輸出: 7

解釋: 在第 2 天 (股票價格 = 2) 的時候買入,在第 3 天 (股票價格 = 6) 的時候賣出, 這筆交易所能獲得利潤 = 6-2 = 4 。

隨後,在第 5 天 (股票價格 = 0) 的時候買入,在第 6 天 (股票價格 = 3) 的時候賣出, 這筆交易所能獲得利潤 = 3-0 = 3 。

解題思路和第三題類似:

Python代碼實現(用一維數組表示):

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        if len(prices)<1:
            return 0
        if len(prices)//2 <= k:
            maxProfit = 0
            for i in range(1,len(prices)):
                if(prices[i-1]<prices[i]):
                    maxProfit+=prices[i]-prices[i-1]
            return maxProfit

        dp = [0]*(k+1)
        v = [prices[0]]*(k+1)
        for i in range(1,len(prices)):
            for t in range(1,k+1):
                v[t] = min(v[t], prices[i] - dp[t - 1])
                dp[t] = max(dp[t], prices[i] - v[t])

        return dp[k]
  • C++實現:
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(!prices.size()) return 0;
        if(k >= prices.size()/2)
        {
            int maxsumval = 0;
            for(int i = 1; i < prices.size(); i++)
                if(prices[i] > prices[i - 1])
                    maxsumval += prices[i] - prices[i - 1];
            return maxsumval;
        }
        vector<int> dp(k + 1, 0);
        vector<int> v(k + 1, prices[0]);
        for(int i = 1; i < prices.size(); i++)
        {
            for(int t = 1; t <= k; t++)
            {
                v[t] = min(v[t], prices[i] - dp[t - 1]);
                dp[t] = max(dp[t], prices[i] - v[t]);  
            }
        } 
        return dp[k];

    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章