买卖股票系列问题——DP

首先我们知道动态的规划的核心就是穷举状态,然后我们在状态中选择最优解,对于卖卖股票的系列问题,我们就举出可能会出现的所有状态

  • 当前是否持有股票,1表示当前持有股票,0表示当前不持有股票
  • 当前天数,表示这是第几天的结果,我们求的应该是最后一天的最大利润
  • 进行买卖的次数,可能会限制卖卖的次数
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)
  • 对应着不同的状态说明是有着不同的选择,有三种选择,买入,卖出,无操作,用buy,sell,rest来表示三种选择
  • 选择是有限制的,比如我们的buy必须在sell之后,sell必须在bug之后,rest操作也应该分为两种状态,一种是buy之后rest(持有股票),一种是sell之后rest(没有持有股票),而且对应着交易次数的限制,也就是说buy在k>0的前提下操作

穷举框架

dp[i][k][0 or 1]
0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,0 和 1 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)
  • 我们最终想求的答案是dp[n-1][K][0],也就是最后一天,最多允许K次交易,最多获取多少利润
  • 为什么不是dp[n-1][K][0],因为在最后一天,我们将手里股票卖出去的利润肯定比放在手里多

状态转换图

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max( 今天选择 rest,        今天选择 sell       )
  • 关于今天我们没有持有股票,可能昨天我们也是没有持有股票,那么就是rest
  • 关于今天我们没有持有股票,可能昨天我们是持有股票的,那么就是是sell,我们是在buy是时候减少k,因为我们第一次交易肯定是买股票,因为交易是从 buy 开始,如果 buy 的选择不改变交易次数 k 的话,会出现交易次数超出限制的的错误。

关于base case

dp[-1][...][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。

dp[-1][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。

dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

121买卖彩票的最佳时期

  • 套模板,这道题其实就是要求我们k是1
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) 
            = max(dp[i-1][1][1], -prices[i])
解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。

现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])
class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int dp[][]=new int[n][2];
        for(int i=0;i<n;i++){
            if (i-1==-1){
                dp[i][1]=-prices[i];
                dp[i][0]=0;
                continue;
            }
            dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
        }
        return dp[n-1][0];
    }
}

  • 也就是 k 为正无穷,但含有交易冷冻期的情况

如果 k 为正无穷,那么就可以认为 k 和 k - 1 是一样的。可以这样改写框架

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

我们发现数组中的 k 已经不会改变了,也就是说不需要记录 k 这个状态了:
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])

关于冷冻期的改变

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])
解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。
class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        int dp[][]=new int [n][2];
        //省略k 因为k为无穷大
        //买入的时候需要隔一天
        for(int i=0;i<n;i++){
            if(i-1==-1){
                dp[i][0]=0;
                dp[i][1]=-prices[i];
                continue;
            }
            if(i-2==-1){
                dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
                dp[i][1]=Math.max(dp[i-1][1],0-prices[i]);
                continue;
            }
            dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1]=Math.max(dp[i-1][1],dp[i-2][0]-prices[i]);
        }
        return dp[n-1][0];
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值