• 买卖股票系列问题——DP


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

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

    穷举框架

    1. dp[i][k][0 or 1]
    2. 0 <= i <= n - 1, 1 <= k <= K
    3. n 为天数,大 K 为交易数的上限,01 代表是否持有股票。
    4. 此问题共 n × K × 2 种状态,全部穷举就能搞定。
    5. for 0 <= i < n:
    6. for 1 <= k <= K:
    7. for s in {0, 1}:
    8. dp[i][k][s] = max(buy, sell, rest)
    • 我们最终想求的答案是dp[n-1][K][0],也就是最后一天,最多允许K次交易,最多获取多少利润
    • 为什么不是dp[n-1][K][0],因为在最后一天,我们将手里股票卖出去的利润肯定比放在手里多

    状态转换图

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

    关于base case

    1. dp[-1][...][0] = 0
    2. 解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0
    3. dp[-1][...][1] = -infinity
    4. 解释:还没开始的时候,是不可能持有股票的。
    5. 因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
    6. dp[...][0][0] = 0
    7. 解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0
    8. dp[...][0][1] = -infinity
    9. 解释:不允许交易的情况下,是不可能持有股票的。
    10. 因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

    121买卖彩票的最佳时期

    • 套模板,这道题其实就是要求我们k是1
    1. dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
    2. dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i])
    3. = max(dp[i-1][1][1], -prices[i])
    4. 解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0
    5. 现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
    6. 可以进行进一步化简去掉所有 k:
    7. dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
    8. dp[i][1] = max(dp[i-1][1], -prices[i])
    1. class Solution {
    2. public int maxProfit(int[] prices) {
    3. int n=prices.length;
    4. int dp[][]=new int[n][2];
    5. for(int i=0;i
    6. if (i-1==-1){
    7. dp[i][1]=-prices[i];
    8. dp[i][0]=0;
    9. continue;
    10. }
    11. dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
    12. dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
    13. }
    14. return dp[n-1][0];
    15. }
    16. }

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

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

    1. dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
    2. dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
    3. = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])
    4. 我们发现数组中的 k 已经不会改变了,也就是说不需要记录 k 这个状态了:
    5. dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
    6. dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])

    关于冷冻期的改变

    1. dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
    2. dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
    3. 解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1
    1. class Solution {
    2. public int maxProfit(int[] prices) {
    3. int n=prices.length;
    4. int dp[][]=new int [n][2];
    5. //省略k 因为k为无穷大
    6. //买入的时候需要隔一天
    7. for(int i=0;i
    8. if(i-1==-1){
    9. dp[i][0]=0;
    10. dp[i][1]=-prices[i];
    11. continue;
    12. }
    13. if(i-2==-1){
    14. dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
    15. dp[i][1]=Math.max(dp[i-1][1],0-prices[i]);
    16. continue;
    17. }
    18. dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
    19. dp[i][1]=Math.max(dp[i-1][1],dp[i-2][0]-prices[i]);
    20. }
    21. return dp[n-1][0];
    22. }
    23. }

  • 相关阅读:
    JavaSE - 数据类型
    Postgresql pgsql 插件之pg_trgm,rum
    番外8.2 --- 后续
    暑期JAVA学习(32)Thread的常用方法
    浅谈SparkSQL基本概念和原理
    一幅长文细学Vue(一)——项目开发工具
    yum命令详解。yum install安装卸载,yum配置仓库
    中国净水机行业市场全面分析及发展趋势调研报告
    ORB-SLAM2从理论到代码实现(十):LoopClosing程序详解
    Vue3异步组件和Suspense
  • 原文地址:https://blog.csdn.net/qq_50985215/article/details/126809184