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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值