题1:
指路:188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
思路与代码:
依旧定义一个二维数组dp[i][]。其中i为第i天,j为第i天的状态。类似于最多买卖四次,本题买卖k次依旧是0为不操作,1为第一次买入,2为第一次卖出,即当j为奇数时,第i天的状态为买入,当j为偶数时,第i天的状态为卖出。又因为最多有k次买卖,一次买卖两个状态,那么j小于2*k+1。关于递推公式,如果不操作则延续之前的状态,如果买入则是dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]),卖出则为dp[i][2]=max(dp[i-1][1] + prices[i], dp[i-1][2])。关于dp数组初始化,买入后手头资金为-peices[i],卖出后手头资金则为0,即dp[0][1]=-prices[i],dp[0][2]=0,dp[0][3]=-prices[0],dp[0][4]=0以此类推,就是j为奇数的时候dp[0][j]=-peices[0]。代码如下:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
// 0为持有,1为非持有
for (int j = 1; j < 2 * k; j += 2) {
dp[0][j] = -prices[0];
}
for (int i = 1; i < prices.size(); i++) {
for (int j = 0; j < 2 * k - 1; j += 2) {
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
}
return dp[prices.size() - 1][k * 2];
}
};
题2:
指路:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
思路与代码:
这个股票问题相对之前做过的其他股票问题多了一个冷静期,即在卖出后的第一天不能买入股票,所以我们在卖出股票方面要拆分讨论,是保持着之前已经卖出的状态还是当天才卖出(卖出后的第一天即为冷静期)。那么我们定义一个数组dp[i][j],其含义依旧为第i天的状态为j,手头的现金为dp[i][j]。在这里结合dp数组和状态分析,我们将dp数组分为四个状态,dp[i][0]表示第i天持股,dp[i][1]表示保持着前面卖出股票的状态,dp[i][2]表示在第i天卖出股票,dp[i][3]表示卖出后的一天冷冻期。在这之中,dp[i][0]持股状态又分为两个状态,即保持着之前买入的状态和在第i天才买入的状态,但在当天买入中也分为两种情况,分别是在保持着卖出状态后买入和在冷冻期后买入。那么分别对其求较大值就是:
dp[i][0]=max(dp[i-1][0], max(dp[i-1][3]-prices[i], dp[i-1][1]-prices[i]));
在保持卖出股票状态中,也分为两种情况保持之前卖出的状态和冷冻期中(卖掉之后冷冻期内不可立即买入),取较大值就是dp[i][1]=max(dp[dp[i-1][1], dp[i-1][3]);在当天卖出股票的状态中只有一种情况,即dp[i][2]=dp[i-1][0]+prices[i];在冷冻期状态中依旧一种状态,即dp[i][3]=dp[i-1][2],因为冷冻期一定是卖出后的第一天股票不可操作手头资金不变。在持股状态下,资金为股票负值,即dp[0][0]=-prices[i]。因为当天的状态依附于之前的状态,那么遍历顺序是从前往后。最后讨论返回值,手头资金最大时股票一定处于卖出状态或冷冻期状态。代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(4, 0));
//0为持股,1为保持卖出,2为当天卖出,卖出+1为冷冻期一天
dp[0][0] = -prices[0];
dp[0][1] = 0;
dp[0][2] = 0;
for (int i = 1; i < n; i++) {
dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]));
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
}
return max(dp[n - 1][3], max(dp[n - 1][1], dp[n - 1][2]));
}
};
题3:
指路:714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
思路与代码:
本题与股票Ⅱ非常相似,不同在于需要付手续费。一买一卖为一笔交易,当卖出的时候需要减去手续费,再与保持卖出状态的dp数组取较大值得到非持股状态的值。其余都很好写。代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
if (prices.size() == 0) return 0;
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); 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] - fee); // 非持股
}
return dp[prices.size() - 1][1];
}
};
股票问题总结:
在买卖股票最佳时机系列问题中,我认为最需要弄清的是几个状态,哪几个状态需要再细分讨论,哪几个状态可以合并处理,在这六个题中体现的淋漓尽致。其次还需要明确最后的返回值,因为卖出股票后手头的资金一定比买入股票后资金多,所以手头资金的最大值应该返回卖出股票状态下的值。然后就是dp数组的初始化需要根据情况而定,不可泛泛而谈初始为0或1。最后需要注意的是在做题过程中需要牢记dp数组的含义。那么到这里,股票问题就暂时告一段落。