文章目录
1 套路分析
1.1 简介
股票问题汇总:
- 121. 买卖股票的最佳时机
- 122. 买卖股票的最佳时机 II
- 123. 买卖股票的最佳时机 III
- 188. 买卖股票的最佳时机 IV
- 309. 最佳买卖股票时机含冷冻期
- 714. 买卖股票的最佳时机含手续费
以上题目分析
- 第1题只允许1次交易,
- 第2题不限制交易次数,
- 第3题限制交易次数为2次,
- 第4题是最泛化的形式,只允许k次交易。
- 第5题加上cool down
- 第6题加上transaction fee
1.2 框架
股票问题状态有三个维度,
- 第一个是当前天数,
- 第二个是到当前所能允许交易的最大次数,(第k次包括第k次买入和第k次卖出)
- 第三个是当前手头股票的持有状态,我们用0代表当前手头没有股票,1代表当前手头已经持有股票。
我们每天都有三种选择:买入股票,卖出股票,和继续持有现有的股票不做操作,我们用buy、sell和rest分别对应这三种选择。
这时候,我们可以有一个粗糙的框架。
dp[i][j][k] 为三维动态规划数组,储存所获的利润
0 <= i <= n-1, i代表当天天数,n代表总的天数
1 <= k <= K, k代表当前天数下所能允许交易的最大次数,K代表总最大允许交易次数
j = 0 or 1, 0代表手头不持有股票,1代表手头持有股票
例子:dp[2][3][1]代表今天是第二题,现在我手上持有股票,至今为止最多进行3次交易。
for i:
for k:
for j:
dp[i][k][j] = max(buy, sell, rest)
return dp[n-1][K][0]
最后返回的是最后一天,最多允许大K次交易,手头股票都卖完了能得到的多少利润
1.3 状态转移方程
今天结束不持有股票,可能是前一天就没持有,今天没操作,也可能是前一天持有但今天卖出。
d p [ i ] [ k ] [ 0 ] = m a x ( d p [ i − 1 ] [ k ] [ 0 ] , d p [ i − 1 ] [ k ] [ 1 ] + p r i c e s [ i ] ) dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][0]=max(dp[i−1][k][0],dp[i−1][k][1]+prices[i])
今天结束持有股票,可能是前一天就持有,今天没操作,也可能是前一天没持有但今天买入。注意理解下面公式中的 k − 1 k-1 k−1,买入+卖出是一对,因为前一天是卖出,所以前一天是第k-1次,今天是第k次。
d p [ i ] [ k ] [ 1 ] = m a x ( d p [ i − 1 ] [ k ] [ 1 ] , d p [ i − 1 ] [ k − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) dp[i][k][1]=max(dp[i−1][k][1],dp[i−1][k−1][0]−prices[i])
1.4 初始状态
dp=[[[0,-float('inf')] for i in range(k+1)] for j in range(n)]
#dp数组的形状是(n,k+1,2)
得益于python数组可以用index=-1代表数组最后一个元素,所以使用dp[0-1]不会出错,本来第一维应该长度应该为n+1,但我们直接拿数组最后一个元素作为第0天的前一天,反正数组的最后一个元素一开始肯定用不到,所以第一维长度为n,后面直接使用动态转移方程即可。
现在我们开始解题了。
2 解题
2.1 第1题,k=1
因为k=1,所以去掉中间k这一维。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
if n<2:return 0
dp=[[0,-float('inf')] for i in range(n)]#dp[i][0]代表第i天,手里没股票的最大利润
dp[0][1]=-prices[0]
for i in range(1,n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
dp[i][1]=max(dp[i-1][1],-prices[i])
return dp[n-1][0]
2.2 第2题,k=无穷
因为k=无穷,所以也可以去掉k这一维。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
dp=[[0,-float('inf')] for i in range(n)]
dp[0][1]=-prices[0]
for i in range(n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i])#跟第一题只有这行不一样
return dp[n-1][0]
2.3 第3题,k=2
现在可以使用k这一维了。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
dp=[[[0,-float('inf')] for i in range(3)] for j in range(n)]
for i in range(n):
for j in range(1,3):
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])
return dp[n-1][2][0]
2.4 第4题,k为任何整数
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n=len(prices)
if n<2:return 0
dp=[[[0,-float('inf')] for i in range(k+1)] for j in range(n)]
for i in range(n):
for j in range(1,k+1):
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])
return dp[n-1][k][0]
2.5 第5题,k为inf,有1天冷冻期
1天冷冻期,只需把dp[i][1]转移公式改为
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 2 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1]=max(dp[i-1][1],dp[i-2][0]-prices[i]) dp[i][1]=max(dp[i−1][1],dp[i−2][0]−prices[i])
即可。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
if n<2:return 0
dp=[[0,-float('inf')] for i in range(n)]
for i in range(n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
dp[i][1]=max(dp[i-1][1],dp[i-2][0]-prices[i])#还是只变动了这一行
return dp[n-1][0]
2.6 第6题,k为inf,有手续费
只需把手续费从利润中扣去即可
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n=len(prices)
if n<2:return 0
dp=[[0,-float('inf')] for i in range(n)]
for i in range(n):
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]-fee)#还是只变动了这一行
return dp[n-1][0]
2.7 总结
以上的解答是不是都非常相似,只需记住第4题的万能模板,其他股票问题都可以从第4题演变出来。以上展示了各个股票问题的解答模板,大家也可以在模板基础上做优化,这就不在本文讨论里了。