参考
代码随想录
题目一:LeetCode 123.买卖股票的最佳时机III
- 确定dp数组下标及其含义
某一天最多存在5个状态:- j = 0:没有操作
- j = 1:第一次买入
- j = 2:第一次卖出
- j = 3:第二次买入
- j = 4:第二次卖出
因此dp[i][j]定义为第i天j状态下所剩的最大现金。
- 确定递推公式
- dp[i][0]
第i天没有操作,那么其状态和第i-1天一样,即dp[i][0] = d0[i-1][0]. - dp[i][1]
注意,dp[i][1]表示的含义是第i天保持第一次买入这个状态所剩的最大现金,并不一定是第i天买入,也可能在之前就买入了。如果第i天买入,那么dp[i][1] = dp[i-1][0]-prices[i];如果在此之前就已经买入,那么dp[i][1] = dp[i-1][1]。最后要保证剩余金额最大(即买股票花费最少),因此dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i]). - dp[i][2]
dp[i][2]表示第i天在第一次卖出这个状态下所剩余的最大现金。如果是在第i天卖出,那么dp[i][2] = dp[i-1][1] + prices[i];如果在i天之前就已经卖出,那么dp[i][2] = dp[i-1][2]。为保证利润最大,dp[i][2] = max(dp[i-1][2],dp[i-1][1] + prices[i])。 - dp[i][3]
dp[i][3]表示第i天保持第2次买入这个状态所剩余的最大现金。如果在第i天第二次买入,那么dp[i][3] = dp[i-1][2] - prices[i];如果在此之前已经买入,那么dp[i][3] = dp[i-1][3],为保证剩余现金最大,dp[i][3] = max(dp[i-1][3],dp[i-1][2] - prices[i])。 - dp[i][4]
dp[i][4]表示第i天保持第二次卖出状态下所剩余的最大现金。如果在第i天卖出,那么dp[i][4] = dp[i-1][3] + prices[i];如果在此之前已经卖出,那么dp[i][4] = dp[i-1][4]。为保证最终的利润最大,dp[i][4] = max(dp[i-1][4],dp[i-1][3] + prices[i]).
最终的递推公式为:
dp[i][0] = dp[i-1][0];
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]);
- dp数组初始化
dp数组的第i状态只涉及到第i-1状态,因此只需要初始化i = 0的状态即可。
- dp[0][0] = 0,第0天没有任何操作,那么剩余的现金为0,这是自定义的,可以定义任意的剩余现金,在最后计算利润的时候减去初始的剩余现金即可,如果初始化为0,最终剩余的现金就是最终利润。
- dp[0][1] = -prices[0],第0天买入后剩余的现金就是 -prices[0],因为在上面已经初始化第0天持有的现金为0
- dp[0][2] = 0,可以理解为第0天买入后又在第0天卖出,所以持有的剩余现金为0
- dp[0][3] = -prices[0],第0天第二次买入,可以理解为第0天第一次买入后卖出,再买入
- dp[0][4] = 0,第0天第一次买入再卖出,再第二次买入再卖出
总结起来,初始化如下:
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
dp[0][3] = -prices[0];
dp[0][4] = 0;
-
确定遍历i顺序
第i天的状态由第i-1天来确定,因此要从前往后遍历。 -
举例推导dp数组
可以看出,最后第二次卖出的利润是要大于等于第一次卖出的利润的,所以最终返回第二次卖出所得的利润即可。
完整的代码实现如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size(),vector<int>(5));
//初始化dp数组
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
dp[0][3] = -prices[0];
dp[0][4] = 0;
//遍历
for(int i = 1; i < dp.size(); i++){
dp[i][0] = dp[i-1][0];
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.back()[4];
}
};
题目二:LeetCode 188.买卖股票的最佳时机IV
这个题在上一个题的基础上加最多买卖两次变成最多买卖k次,其实求解思路是一样的,在上一个题推导递推公式的时候其实就可以发现规律了。
上一个题最多买卖两次,每天最多有5个状态,其中一个是没有操作,剩余4个是分别是每次交易的两个状态,因此很容易得到最多k次交易时,每天有2k+1个状态,其中的1是没有操作,其余每次交易都有两个状态。同样用j来标记每天的状态,当j % 2 == 1时表示当前是第j/2+1
次买入状态,当j % 2 == 0时表示第j/2
次卖出状态,这里当k == 2带入就是上一个题的情况了。
因此,对于最多k次交易,定义dp[i][j]:第i天时j状态下所剩余的最大现金为dp[i][j],其中0 <= j <= 2*k,当j为奇数时表示买入,当j为偶数时表示卖出。
如果是买入(j为奇数)状态,则递推公式为:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]);
如果是卖出(j为偶数)状态,则递推公式为:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + prices[i]);
完整的代码实现如下:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
vector<vector<int>> dp(prices.size(),vector<int>(2 * k + 1));
//初始化dp数组
for(int j = 1 ; j < 2 * k + 1; j += 2) //注意这里是j += 2
dp[0][j] = -prices[0]; //买入状态初始化,卖出状态已经初始化为0
//遍历
for(int i = 1; i < dp.size(); i++){
//dp[i][0] = dp[i-1][0]; //始终保持不变,可以不用
for(int j = 1; j < 2 * k + 1; j += 2){ //注意这里是j += 2
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]); //买入
dp[i][j+1] = max(dp[i-1][j+1],dp[i-1][j] + prices[i]); //卖出
}
}
return dp.back()[2*k];
}
};