LeetCode第 188 题:买股票的最佳时机IV(C++)

188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
在这里插入图片描述
股票系列:
LeetCode第 121 题:买股票的最佳时机(C++)_zj-CSDN博客
LeetCode第 122 题:买股票的最佳时机II(C++)_zj-CSDN博客
LeetCode第 123 题:买股票的最佳时机III(C++)_zj-CSDN博客
LeetCode第 188 题:买股票的最佳时机IV(C++)_zj-CSDN博客
LeetCode第 309 题:最佳买卖股票时机含冷冻期(C++)_zj-CSDN博客
LeetCode第 714 题:买卖股票的最佳时机含手续费(C++)_zj-CSDN博客

LeetCode第 123 题:买股票的最佳时机III(C++)_zj-CSDN博客的基础上,把限制交易两次变成了限制交易k次。

不过到这儿我反而对状态的定义有些迷茫了,天数、是否持有股票都好理解,但是第二个状态k,到底是定义为买入次数呢还是卖出次数呢?

如果定义为买入次数(个人认为更容易理解):

 //max(昨天就是这样--今天休息, 昨天状态也是买入j次-今天卖出)
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
//max(昨天就是这样--今天休息, 昨天状态是买入j-1次-今天又买入)
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);

首先注意两点:
只有昨天不持有股票,今天才能进行买入(因为任何两次交易是不能重叠的);
只有昨天持有股票,今天才可以进行卖出。

第一行要注意:不用考虑dp[i-1][j-1][0],因为dp[i-1][j-1][0]代表昨天买入j-1次,但是昨天没有持股,要想在今天状态变为买入j次,必须今天进行买入,但是今天如果进行买入的话,今天必然就是持股的状态,也就是dp[i-1][j-1][0]只会转移到dp[i][j][1],而不是dp[i][j][0]。

第二行要注意,不用考虑dp[i-1][j][0],因为它代表昨天买入了j次,但是昨天是没有持股的,要想转移到dp[i][j][1](也就是持股)状态,那么从未持股到持股只能再买入一次,但是再买一次买入次数就是j+1,也是就状态会转移到dp[i][j+1][1],而不是dp[i][j][1]。

注意初始化:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if(k == 0)  return 0;
        if(k >= n/2)    return greedy(prices);//退化为无限交易的情况(122题)
        //天数、买入次数状态(k,k-1...2,1,0)、是否持有股票(1,0)
        int dp[n][k+1][2];
        memset(dp, -1e7, sizeof(dp));//不可能的状态都初始化为一个很小的值
        //初始第一天、以及买入次数为0的情况
        dp[0][0][0] = 0;//第一天什么都不做
        dp[0][1][1] = -prices[0];//第一天买入
        for(int i = 1; i < n; ++i)  dp[i][0][0] = 0;//任意一天,只要买入次数为0,收益肯定为0
        
        //从第二天、买入次数为1开始dp
        for(int i = 1; i < n; ++i){//今天可以:什么都不做、卖出(卖出次数增加)、买入(状态变为持有股票)
            for(int j = 1; j <= k; ++j){
                //max(昨天就是这样--今天休息, 昨天状态也是买入j次且持有股票-今天卖出)
                dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
                //max(昨天就是这样--今天休息, 昨天状态是买入j-1次且不持有股票-今天又买入)
                dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
            }
        }
        int res = 0;
        for(int i = 0; i <= k; ++i){
            res = max(res, dp[n-1][i][0]);
        }
        return res;
    }

    int greedy(vector<int> &prices){
        int n = prices.size();
        int res = 0;
        for(int i = 1; i < n; ++i){
            if(prices[i] > prices[i-1]) res += prices[i] - prices[i-1];
        }
        return res;
    }
};

那么这一题也是可以进行状态压缩的,因为每一天的状态只依赖于前一天的状态(不过这儿仔细想想也是比较难理解的),这儿遍历的时候,对于买入次数k来说正序逆序都是不影响的,不会产生值的覆盖导致计算出错:

//max(昨天就是这样--今天休息, 昨天状态也是买入j次且持有股票-今天卖出)
dp[j][0] = max(dp[j][0], dp[j][1] + prices[i]);//这儿的计算只会覆盖掉dp[j][0],刚好不影响下一步的计算
//max(昨天就是这样--今天休息, 昨天状态是买入j-1次且不持有股票-今天又买入)
dp[j][1] = max(dp[j][1], dp[j-1][0] - prices[i]);
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if(k == 0)  return 0;
        if(k >= n/2)    return greedy(prices);//退化为无限交易的情况(122题)
        //天数、买入次数状态(k,k-1...2,1,0)、是否持有股票(1,0)
        int dp[k+1][2];//两个状态是:买入次数,是否持股
        memset(dp, -1e7, sizeof(dp));//不可能的状态都初始化为一个很小的值
        //初始第一天、以及买入次数为0的情况
        dp[0][0] = 0;//第一天什么都不做
        dp[1][1] = -prices[0];//第一天买入
        
        //从第二天、买入次数为1开始dp
        for(int i = 1; i < n; ++i){//今天可以:什么都不做、卖出(卖出次数增加)、买入(状态变为持有股票)
            for(int j = 1; j <= k; ++j){
                //max(昨天就是这样--今天休息, 昨天状态也是买入j次且持有股票-今天卖出)
                dp[j][0] = max(dp[j][0], dp[j][1] + prices[i]);
                //max(昨天就是这样--今天休息, 昨天状态是买入j-1次且不持有股票-今天又买入)
                dp[j][1] = max(dp[j][1], dp[j-1][0] - prices[i]);
            }
        }
        int res = 0;
        for(int i = 0; i <= k; ++i){
            res = max(res, dp[i][0]);
        }
        return res;
    }

    int greedy(vector<int> &prices){
        int n = prices.size();
        int res = 0;
        for(int i = 1; i < n; ++i){
            if(prices[i] > prices[i-1]) res += prices[i] - prices[i-1];
        }
        return res;
    }
};

这儿也可以像第一处的代码那样采用比较特殊的初始化来简化代码,不过会变得不容易理解。

当然第二维的状态定义为卖出次数的话,应该也是可以的,只不过状态转移会有一小点不同,因为不太好初始化,至少要第二天才会产生第一次卖出,这儿就不写了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值