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