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;
}
};
这儿也可以像第一处的代码那样采用比较特殊的初始化来简化代码,不过会变得不容易理解。
当然第二维的状态定义为卖出次数的话,应该也是可以的,只不过状态转移会有一小点不同,因为不太好初始化,至少要第二天才会产生第一次卖出,这儿就不写了。