LeetCode股票問題之套用DP框架

LeetCode 股票問題的一種通用解法之後,我便寫了一個框架闖天涯,現來記錄我直接使用三維DP框架怎麼解決 Leet Code 的六個股票問題

通過上篇文章,可以肯定的是,這六道題目一定存在共性,且能夠使用一種框架解決。

接下來,我們從最難的也最泛化的 買賣股票的最佳時機 IV 入手

首先,可以想到的做法是暴力。

其次,使用DP,那麼我們就要 1. 定義狀態 2. 狀態轉移方程

第一次嘗試定義狀態:

使用一維數組表示狀態
MP[i],表示第 i 天的 max profit, MP[n-1] 即爲所求。

寫狀態轉移方程:

每一天都會有三個操作,買入,賣出或不作爲。那麼 MP[i] 應該等於前一天的最大利潤減去第 i 天買入股票 的價格,或者加上第 i 天賣出股票的價格…那麼狀態轉移方程應該是

MP[i] = MP[i-1] + (-prices[i])  買入
        MP[i-1] + prices[i]     賣出

那麼,由題,我們只能完成一次交易後,再進行下一次交易。即只有不持股的時候才能買入,持股的時候才能賣出。可以發現一維狀態會丟失很多信息,我們需要增加狀態的維度,來表示更多信息:
MP[i][j], j = 0 或 1 來表示是否持股, 表示第 i 天持股或未持股的最大利潤

狀態轉移方程就可以這麼寫:

MP[i][0] = max(MP[i-1][0], MP[i-1][1] + prices[i])  # 括號前者表示前一天不作爲,後者表示前一天持股並賣出
MP[i][1] = max(MP[i-1][1], MP[i-1][0] - prices[i])  # 括號前者表示前一天不作爲,後者表示前一天未持股並買入

可以得到,增加狀態的一個維度,可以讓狀態具備更多信息。
那麼,可以發現,現在的狀態並沒有記錄交易次數,因此,我們乾脆再增加狀態一個維度,讓其表示可交易次數:
MP[i][k][j], k = 0~K,表示第 i 天持股或未持股且能交易 k 次的最大利潤

狀態轉移方程:

for i: 0~n-1
  for k: K~0
    MP[i][k][0] = max(MP[i-1][k][0], MP[i-1][k][1] + prices[i])
    MP[i][k][1] = max(MP[i-1][k][1], MP[i-1][k-1][0] - prices[i])  我們定義在買入的時候減少交易次數

上述狀態以及狀態轉移方程就是此類問題的標準框架了,對於不同的問題,稍加修改即可。

下面來確定初始狀態

MP[-1][k][0] = 0    表示還沒開始時,最大利潤爲0
MP[-1][k][1] = -inf  表示未開始時,用負無窮表示不可能持股
MP[i][0][0] = 0     表示 k = 0 時,不允許交易,最大利潤爲0
MP[i][0][1] = -inf   表示 k = 0 時,不允許交易,負無窮表示不能持股

有了之上的分析,我們就可以碼出代碼咯

def maxProfit(k, prices):
    if not prices or not k: return 0
    
    mp = [[[0, float('-inf')] for _ in range(k+1)] for _ in range(len(prices))]
    for i in range(len(prices)):
        for kk in range(k, 0, -1):
            mp[i][kk][0] = max(mp[i-1][kk][0], mp[i-1][kk][1] + prices[i])
            mp[i][kk][1] = max(mp[i-1][kk][1], mp[i-1][kk-1][0] - prices[i])
    return mp[len(prices)-1][k][0]

在 leetcode 運行後,發現竟然超時了。

原因是,k 非常大的時候,就相當於沒有交易次數限制了,此時使用貪心算法是最優的。

k 多大的時候相當於沒有交易次數限制呢?
一次交易最少需要買入一天和賣出的一天,即共需 2 天,當 k >= n//2 時,就相當於沒有交易次數限制咯~

我們使用貪心處理沒有交易限制的情況:

def maxProfit(k, prices):
    if not prices or not k: return 0
    if k > len(prices)//2:
        maxPro = 0
        for sell_day in range(1, len(prices)):
            maxPro = maxPro + max(prices[sell_day] - prices[sell_day-1], 0)
        return maxPro
    
    mp = [[[0, float('-inf')] for _ in range(k+1)] for _ in range(len(prices))]
    for i in range(len(prices)):
        for kk in range(k, 0, -1):
            mp[i][kk][0] = max(mp[i-1][kk][0], mp[i-1][kk][1] + prices[i])
            mp[i][kk][1] = max(mp[i-1][kk][1], mp[i-1][kk-1][0] - prices[i])
    return mp[len(prices)-1][k][0]

對於 121.買賣股票的最佳時機
此時 K = 1, 直接套 DP 狀態模板,刷刷刷~ ~ ~ 直接出答案,優化什麼的之後再說唄

def maxProfit(prices):
    k = 1
    if not prices: return 0
    
    mp = [[[0, float('-inf')] for _ in range(k+1)] for _ in range(len(prices))]
    for i in range(len(prices)):
        for kk in range(k, 0, -1):
            mp[i][kk][0] = max(mp[i-1][kk][0], mp[i-1][kk][1] + prices[i])
            mp[i][kk][1] = max(mp[i-1][kk][1], mp[i-1][kk-1][0] - prices[i])
    return mp[len(prices)-1][k][0]

對於122.買賣股票的最佳時機 II
此時 K = infinity,我們認爲 k-1 ≈ k,則可見 k 對狀態不起作用,去掉這一維度,刷刷刷~ ~ ~直接出答案!

def maxProfit(prices):
    mp = [[0, float('-inf')] for _ in range(len(prices))]
    for i in range(len(prices)):
        mp[i][0] = max(mp[i-1][0], mp[i-1][1] + prices[i])
        mp[i][1] = max(mp[i-1][1], mp[i-1][0] - prices[i])
    return mp[len(prices)-1][0]

這題,也可以套用模版中的貪心部分,dong~ ba~ chua~ 直接出答案

def maxProfit(prices):
    if not prices: return 0
    maxPro = 0
    for sell_day in range(1, len(prices)):
        maxPro = maxPro + max(prices[sell_day] - prices[sell_day-1], 0)
    return maxPro

對於123.買賣股票的最佳時機 III
此時 K = 2, 直接套模板,cua~ cua~ cua~ 直接出答案!

def maxProfit(prices):
    k = 2
    if not prices: return 0
    
    mp = [[[0, float('-inf')] for _ in range(k+1)] for _ in range(len(prices))]
    for i in range(len(prices)):
        for kk in range(k, 0, -1):
            mp[i][kk][0] = max(mp[i-1][kk][0], mp[i-1][kk][1] + prices[i])
            mp[i][kk][1] = max(mp[i-1][kk][1], mp[i-1][kk-1][0] - prices[i])
    return mp[len(prices)-1][k][0]

對於309.最佳買賣股票時機含冷凍期
此時 K = infinity, 冷凍期含義是 如果我們在第 i-1 天賣出就不能在第 i 天購買股票。因此第 i 天買入的時候,狀態應該是第 i-2 天轉換來的,代碼如下

def maxProfit(prices):
    if len(prices) < 2: return 0

    mp = [[0, float('-inf')] for _ in range(len(prices))]
    for i in range(len(prices)):
        mp[i][0] = max(mp[i-1][0], mp[i-1][1] + prices[i])
        mp[i][1] = max(mp[i-1][1], mp[i-2][0] - prices[i])
    return mp[len(prices)-1][0]

對於714.買賣股票的最佳時機含手續費
此時 K = infinity, 每次交易需要繳納手續費,只需在買入或賣出的時候減去手續費即可. 套用模版,diu~ diu~ diu~ 出答案~

class Solution:
    def maxProfit(prices, fee) -> int:
        mp = [[0, float('-inf')] for _ in range(len(prices))]
        for i in range(len(prices)):
            mp[i][0] = max(mp[i-1][0], mp[i-1][1] + prices[i])
            mp[i][1] = max(mp[i-1][1], mp[i-1][0] - prices[i] - fee)
        return mp[len(prices)-1][0] 

總結,leetcode第188題是最具範性的股票問題,自己根據此題推出三維 DP 狀態以及狀態轉移方程模板,只需稍微修改即可通過其他股票問題。

時間和空間複雜度平均 O(n^2)

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