LeetCode 309. Best Time to Buy and Sell Stock with Cooldown【动态规划+股票系列】⭐⭐⭐⭐⭐

题目描述

在这里插入图片描述

这道题目需要理解清楚。

知识点

动态规划的股票系列

我的实现

结果

在这里插入图片描述

码前思考

这道题目我想了挺久的,我是用类似于背包的思想去解题的。

定义一个二维数组dp[][]dp[i][j]表示在前i件物品的前提下,若【最后一步有效操作】是j操作,能获得的最大收益。 这里的【有效操作】就是跳过了那些在某天不买卖股票的情况。

为什么会想到这么抽象的一个定义呢?

假设现在是第i天,那么累积的【有效操作序列】,永远是以或者或者冷却结尾的。

如果在当天不进行某项操作,类似于背包问题的思想一样,只能说明当天进行某种操作是不值得的,前面的那些操作更加值得!

然后再是分解子问题,书写状态转移方程:

  • 如果当天是结尾的序列,那么可以是继承上一次,或者从上一次冷却而来,抑或是之前从未操作过,现在第一次

  • 如果当天是结尾的序列,那么可以是继承上一次卖·,或者是从上一次而来!

  • 如果当天是冷却结尾的序列,那么可以是继承了上一次冷却,或者是从上一次而来 。

代码实现

//采用背包的思想解题
//dp[i,j]表示在有前i件物品的前提下,若【最后一步】是j操作,能获得的最大收益
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int inf = 0x3fffffff;
        //注意一共有3种操作
        int len = prices.size();
        vector<vector<int>> dp(len+1,vector<int>(3,-inf));
        int ans = 0;
        //开始进行递推
        for(int j=1;j<=len;j++){
            //如果是买的话,那么与前面的买和cooldown进行比较,还有只买自己(官方题解混杂了冷冻期和休息的概念,我感到不适,所以单独拿出只买自己的情况,其实冷冻期就是强制的一天休息罢了,所以还是从休息出发比较好)
            dp[j][0] = max(dp[j-1][0],dp[j-1][2]-prices[j-1]);
            dp[j][0] = max(dp[j][0],-prices[j-1]);
            //如果是卖的话,那么与前面的卖和买做对比
            dp[j][1] = max(dp[j-1][1],dp[j-1][0]+prices[j-1]);
            //如果是cooldown,那么与前面的cooldown和卖做对比
            dp[j][2] = max(dp[j-1][2],dp[j-1][1]);
        }

        //遍历得到最大值
        for(int j=0;j<=2;j++){
            ans = max(dp[len][j],ans);
        }

        return ans;
    }
};

码后反思

  1. 我的思路主要是从01背包那边来的,因为我看见这种 O ( 3 n ) O(3^n) O(3n)的暴力解法时,已经习惯性地会使用背包来求解了;
  2. 第一次提交超时了,因为第一次提交时写的代码是枚举第一次买的位置,时间复杂度为 O ( n 2 ) O(n^2) O(n2)了,后来发现所谓的第一次买,其实是可以用下面这个代码内涵的:
    //如果是买的话,那么与前面的买和cooldown进行比较,还有只买自己
    dp[j][0] = max(dp[j-1][0],dp[j-1][2]-prices[j-1]);
    dp[j][0] = max(dp[j][0],-prices[j-1]);
    

网友超赞题解

DP 状态的选择

  • 可以用一个三维数组,i 表示天,j表示是否持有股票,k表示是否是冷冻期
  • 也可以用一个二维数组,dp[i][j]:i 表示天,j为 0,1,2:0表示持股,1表示不持股,2表示处于冷冻天
  • 也可以用三个一维数组,分别代表第i天,3种选择:卖出、买进、休息,对应的最大收益。
  • 也可以按手中是否持有股票,用两个一维数组,分别代表第 i 天,持有和没持有的最大收益

也就是说,在选择DP状态的定义时,可以尝试着降维。

状态转移

  • hold[i] : 在第 i 天的结束时,手中持有股票,此时的最大收益

    • 有股,有两种可能:今天休息、或买了股票
    • 可能是昨天持有了,今天休息,也可能是前几天卖了,今天买的
    • hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i])
  • unhold[i] : 第 i 天的结束时,手中没有股票,此时的最大收益

    • 没股,有两种可能:今天休息、或卖了股票
    • 可能是昨天也没持有,今天休息,也可能是昨天持有,今天卖了
    • unhold[i] = Math.max(unhold[i -1], hold[i - 1] + prices[i])
  • 目标是求 unhold[n-1] ( n:0 1 2 3 … )

base case

  • hold[0] = -prices[0] 第0天买股票,收益-prices[0]
  • hold[1] = Math.max(-prices[0], -prices[1]) 第1天持有着股票,可能是昨天买的,今天休息,也可能是昨天休息,今天买的
  • unhold[0] = 0 第0天没有持有股票,就是休息,收益 0 元

代码

时间复杂度:两个dp数组发生状态转移,花费 O ( 2 n ) O(2n) O(2n) ,总的来说是 O ( n ) O(n) O(n)

const maxProfit = (prices) => {
  const n = prices.length;   // n天
  if (n == 0) return 0
  let hold = new Array(n);   // 第i天持有股票的最大收益
  let unhold = new Array(n); // 第i天不持有股票的最大收益
  hold[0] = -prices[0];      // 第0天 买了股票的收益
  unhold[0] = 0
  for (let i = 1; i < n; i++) {
    if (i == 1) {            // base case
      hold[i] = Math.max(hold[i - 1], -prices[1]);
    } else {
      hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i]);
    }
    unhold[i] = Math.max(unhold[i - 1], hold[i - 1] + prices[i]); 
  }
  return unhold[n - 1];
};

作者:hyj8
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/dp-zhuang-tai-de-ding-yi-you-liang-chong-fang-fa-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

//所谓的冷冻期就是多了一天休息罢了,所以还是从休息出发理解
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len == 0){
            return 0;
        }
        vector<int> hold(len);  //hold表示当前持有股票
        vector<int> unhold(len);    //unhold表示当前不持有股票

        //dp套路:初始化
        hold[0] = -prices[0];   
        unhold[0] = 0;

        for(int i=1;i<len;i++){
            if(i==1){
                hold[1] = max(hold[0],-prices[1]);//昨天持有,今天休息;前几天卖了,今天买入。
            }else{
                hold[i] = max(hold[i-1],unhold[i-2]-prices[i]);
            }
            unhold[i] = max(hold[i-1]+prices[i],unhold[i-1]);
        }

        return unhold[len-1];
    } 
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值