本题的重点是:只能在前面某一天买入,后面某一天卖出。要不就是不买入,收益为0
dp数组含义
本题两个状态:持有股票、不持有股票
最后返回dp[n-1][0],表示最后一天不持有股票,所获得最多的现金
递推公式
第i天不持有股票所得最多现金 = max(第i-1天不持有股票手上的现金, 第i-1天手上持有股票、但是在第i天卖出手上的现金)
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金, 第i-1天手上没有股票、但是在第i天买入股票手上的现金)
dp[i][1] = max(dp[i-1][1], 0 - prices[i]) // i-1天不持有,第i天持有,那一定是第i天买入
初始化
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
// dp[i][1] 表示第i天持有股票所得最多现金
// dp[i][0] 表示第i天不持有股票所得最多现金
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][1] = 0 - prices[0]; // 第0天持有股票,手上的钱就是买入后的负数
dp[0][0] = 0; // 第0天不持有股票,手上的钱是0
for(int i = 1; i < n; i++){
// 第i天不持有股票所得最多现金 = max(第i-1天没有股票手上的现金, 第i-1天手上持有股票、但是在第i天卖出手上的现金)
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
// 第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金, 第i-1天手上没有股票、但是在第i天买入股票手上的现金)
dp[i][1] = max(dp[i-1][1], 0 - prices[i]);
}
return dp[n-1][0];
}
};
第二种dp方法
第i天的最大收益 = max(第i-1天的最大收益,第i天的价格 - 前i-1天的最低价)
dp[i] = max(dp[i-1], prices[i] - min_val)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int> dp(n, 0); // dp[i]:第i天的最大收益
dp[0] = 0;
int min_val = prices[0];
for(int i = 1; i < n; i++){
min_val = min(min_val, prices[i]);
dp[i] = max(dp[i-1], prices[i] - min_val);
}
return dp[n-1];
}
};
本题的重点是:可以在任意一天进行买入或卖出,且不限制买入和卖出的次数
dp数组含义
本题两个状态:持有股票、不持有股票
最后返回dp[n-1][0],表示最后一天不持有股票,所获得最多的现金
递推公式
第i天不持有股票所得最多现金 = max(第i-1天不持有股票手上的现金, 第i-1天手上持有股票、但是在第i天卖出手上的现金)
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金, 第i-1天手上没有股票、但是在第i天买入股票手上的现金)
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
初始化
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
// dp[i][1] 表示第i天持有股票手上最多的现金
// dp[i][0] 表示第i天不持有股票所得最多现金
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][1] = 0 - prices[0];
dp[0][0] = 0;
// 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])
for(int i = 1; i < n; 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-1][0] - prices[i]);
}
return dp[n-1][0];
}
};
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int ans = 0;
for(int i = 1; i < n; i++){
if(prices[i] > prices[i-1]) ans += prices[i] - prices[i-1];
}
return ans;
}
};
本题就是在122. 买卖股票的最佳时机 II的基础上加上了冷冻期
122. 买卖股票的最佳时机 II有两个状态:持有股票后的最多现金,和不持有股票的最多现金
而本题中,我们将状态分为三种:持有股票、不持有股票处于冷冻期、不持有股票不处于冷冻期
dp数组含义
递推公式
第i天不持有股票,且不在冷冻期能获得的最大收益 = max(前一天处于冷冻期的收益,前一天不持有股票且不在冷冻期的收益)
dp[i][0] = max(dp[i-1][1], dp[i-1][0])
第i天不持有股票,且在冷冻期能获得的最大收益 = 前一天持有股票卖出的收益
dp[i][1] = dp[i-1][2] + prices[i]
第i天持有股票能获得的最大收益 = max(前一天持有股票没卖,前一天不持有股票且不在冷冻期但是买入股票)
dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i])
初始化
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(3, 0));
dp[0][0] = 0; // 不持有股票,不在冷冻期
dp[0][1] = 0; // 不持有股票,在冷冻期
dp[0][2] = 0 - prices[0]; // 持有股票
for(int i = 1; i < n; i++){
dp[i][0] = max(dp[i-1][1], dp[i-1][0]);
dp[i][1] = dp[i-1][2] + prices[i];
dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i]);
}
return max(dp[n-1][0], dp[n-1][1]);
}
};
dp数组含义
dp[i][j]:表示第i天,处于状态j时,所能获得的最大收益
递推公式
状态0:没买卖过股票
很明显,图中没有入度,无需更新,所能获得的最大收益保持为0
状态1:第一次持有股票
第一次处于持有股票状态的最大收益 = max(前一天就处于第一次持有股票状态, 前一天没做操作、今天才买入股票)
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
状态2:第一次持有股票、又卖出
第一次处于持有股票、又卖出状态的最大收益 = max(前一天就处于第一次持有股票、又卖出状态, 前一天处于持有股票、今天才卖出)
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])
状态3:第二次持有股票
第二次处于持有股票状态的最大收益 = max(前一天就处于第二次持有股票状态, 前一天处于第一次持有股票并卖出、今天才买入股票)
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])
状态4:第二次不持有股票、又卖出
第二次处于持有股票、又卖出状态的最大收益 = max(前一天就处于第二次持有股票、又卖出状态, 前一天处于第二次持有股票、今天才卖出)
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])
初始化
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(5, 0));
dp[0][1] = 0 - prices[0];
dp[0][3] = 0 - prices[0];
for(int i = 1; i < n; i++){
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i]);
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i]);
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]);
}
return dp[n-1][4];
}
};
观察可以发现,二维数组中,计算当前层数据时,只需要使用上方元素,以及上方相邻元素即可,和背包问题压缩维度原理一样,本题也可以压缩维度,逆序更新即可。因为计算下一行时,没有用到后面的元素,先更新后面的没有影响
其实本题不改变更新顺序也行
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int> dp(5, 0);
dp[1] = 0 - prices[0];
dp[3] = 0 - prices[0];
for(int i = 1; i < n; i++){
dp[4] = max(dp[4], dp[3] + prices[i]);
dp[3] = max(dp[3], dp[2] - prices[i]);
dp[2] = max(dp[2], dp[1] + prices[i]);
dp[1] = max(dp[1], dp[0] - prices[i]);
}
return dp[4];
}
};
上题中,最多两笔交易时,有5个状态;本题最多k笔交易,会有2*k+1个状态,因为完成k笔交易,需要k次买入和k次卖出,再加上不做任何操作的状态,总共有2*k+1个状态
和上题一样,我们把除了0以外的奇数状态定为持有股票状态,而偶数状态定为卖出股票的状态
dp数组含义
dp[i][j]:表示第i天,处于状态j时,所能获得的最大收益,强调一下,第i天可以经过多种状态,因为不限制每天卖卖股票的次数,就像下面初始化时所描述的一样
初始化
我们可以看出,奇数状态为买入状态,统一初始化为-prices[0],偶数状态为0
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2 * k + 1, 0));
for(int i = 1; i < 2 * k + 1; i += 2){
dp[0][i] = 0 - prices[0];
}
for(int i = 1; i < n; i++){
for(int j = 1; j < 2 * k + 1; j++){
if(j & 1) dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i]);
else dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + prices[i]);
}
}
return dp[n-1][2*k];
}
};
压缩维度
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector<int> dp(2 * k + 1, 0);
for(int i = 1; i < 2 * k + 1; i += 2){
dp[i] = 0 - prices[0];
}
for(int i = 1; i < n; i++){
for(int j = 2 * k; j > 0; j--){
if(j & 1) dp[j] = max(dp[j], dp[j-1] - prices[i]);
else dp[j] = max(dp[j], dp[j-1] + prices[i]);
}
}
return dp[2*k];
}
};