LeetCode 123.买卖股票的最佳时机III
思路:
这题的关键在于限制了买入和卖出的次数,而按照在122. 买卖股票的最佳时机 II的做法,仅仅每次累计之前的金额是无法控制买入和卖出次数的,所以有没有办法是能记录次数的?有,只要为每次买入和卖出单独分配一个下标就可以了。定义下标:dp[i][j]表示第i天j状态下所能得到的最大金额,j一共有5个状态:
- 0:不进行任何操作
- 1:第一次买入
- 2:第一次卖出
- 3:第二次买入
- 4:第二次卖出
通过以上几个状态,就可以记录在给定的买入和卖出次数下所能获得的最大金额了。然后初始化dp,dp[0][0]表示在当天不进行任何操作,所以没有买入和卖出,所持最大金额为0。dp[0][1]表示在当天进行买入,所持最大金额为-prices[0]。dp[0][2]表示在第一天第一次卖出,因为第一天没有持有股票,当然也无法卖出,当然买了当天股票再卖出也是等于没有卖,所以所持最大金额也是0。dp[0][3]表示第二次买入,这个时候就和当天买入再马上卖出然后又马上再买入了,所以所持最大金额也是-prices[0]。dp[0][4]和dp[0][2]的情况一样,不过多做解释。
接下来比较关键,需要确认递推公式。由于有5个状态,每个状态都需要一个递推公式。
- dp[i][0] = dp[i-1][0]。表示什么都不做,所以和前一天一样,其实这一步可以省略
- dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1])。进入第一次买入状态的两种情况: 1. 第一次买入,所持有最大金额等于前一天没有买股票的状态,减去当天股票的价格。 2. 前一天已经买入了股票,所持最大金额为前一天买了股票的最大金额
- dp[i][2] = max(dp[i-1][1] + prices[i], dp[i-1][2])。进入第一次卖出状态的两种情况:1. 第一次卖出,所持最大金额等于前一天第一次持有股票时的金额dp[i-1][1],加上当天股票的价格prices[i]。 2. 前一天已经卖出了,所持有金额等于前一天卖出的金额dp[i-1][2]。
- dp[i][3] = max(dp[i-1][2] - prices[i], dp[i-1][3])。情况和第一次买入一样,唯一的区别是该状态必须由第一次卖出(dp[i-1][2])推导而来,以确保这个状态是第二次买入的状态。
- dp[i][4] = max(dp[i-1][3] + prices[i], dp[i-1][4])。情况和第一次卖出一样,唯一的区别是该状态必须由第二次买入(dp[i-1][3])推导而来,以确保这个状态是第二次卖出的状态。
最后返回最后一天最后一次卖出时所持有的最大金额即可。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
// 定义下标:dp[i][j]表示第i天j状态下所能得到的最大金额
// j有5个状态:
// 0:不进行任何操作
// 1: 第一次买入
// 2:第一次卖出
// 3:第二次买入
// 4:第二次卖出
vector<vector<int>> dp(prices.size(), vector<int>(5));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++)
{
dp[i][0] = dp[i-1][0];
// 进入第一次买入状态的两种情况: 1. 第一次买入 2. 前一天已经买入了
dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1]);
// 进入第一次卖出状态的两种情况:1. 第一次卖出 2. 前一天已经卖出了
dp[i][2] = max(dp[i-1][1] + prices[i], dp[i-1][2]);
dp[i][3] = max(dp[i-1][2] - prices[i], dp[i-1][3]);
dp[i][4] = max(dp[i-1][3] + prices[i], dp[i-1][4]);
}
return dp[prices.size()-1][4];
}
};
LeetCode 188. 买卖股票的最佳时机 IV
思路:
和上一题有异曲同工之处,dp下标定义以及递推公式都是一样的不再赘述,主要区别注意买卖次数不同时如何操作的。根据买卖次数k,定义dp数组中状态数量为k*2+1个,乘2表示每次都包含买入和卖出两种状态,在奇数状态下买入,偶数状态下卖出。所以递推公式中,j为奇数时对应第一次买入的递推公式,j为偶数时对应第一次卖出的递推公式。每次更新状态j都需要依赖上一个状态j-1,以保证当前状态是由上一个状态推导来的。注意j每次循环都要加2而不是加1。
代码:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
// 定义下标:dp[i][j]表示第i天j状态下所能得到的最大金额
// j有k*2+1个状态:
// 0:不进行任何操作
// 1: 第一次买入
// 2:第一次卖出
// 3:第二次买入
// 4:第二次卖出
// ...以此类推
vector<vector<int>> dp(prices.size(), vector<int>(2*k + 1));
for (int j = 0; j < k; j++)
dp[0][j * 2 + 1] = -prices[0];
for (int i = 1; i < prices.size(); i++)
{
//dp[i][0] = dp[i-1][0];
for (int j = 1; j < 2*k; j += 2)
{
dp[i][j] = max(dp[i-1][j-1] - prices[i], dp[i-1][j]);
dp[i][j+1] = max(dp[i-1][j] + prices[i], dp[i-1][j+1]);
}
}
return dp[prices.size()-1][2*k];
}
};