今日感悟:细节决定成败,一定要尽可能地想到每道题的每一个细节,从而理解清楚一道题。
任务日期:7.17
题目一链接:188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
思路:本题在第三个买卖股票类型上升级为最多可以买卖k次股票。由于**最多**买卖k次的dp数组状态有2 * k + 1个(因为进行零次买卖的状态是零),所以要求我们区分偶数(不持有股票)状态和技术(持有股票)状态的区别从而进行初始化和遍历dp数组。
代码:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len,vector<int>(2 * k + 1,0));//dp[][0]:状态零是不进行操作的最大现金,因此dp数组的值都是零
//初始化dp数组:第零天的奇数状态是持有,偶数状态是不持有
for(int i = 1;i < 2 * k + 1;i += 2) {
dp[0][i] = -prices[0];//第零天如果持有那么此时最大现金就是-prices[0]
}
//确定遍历顺序和递推公式dp[i][奇数] = max(dp[i - 1][奇数],dp[i - 1][奇数 - 1] - prices[i])
for(int i = 1;i < len; i ++) { //因为要从前一天(i - 1)开始推导,所以i必须从1开始
for(int j = 1;j < 2 * k;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[len - 1][2 * k];
}
};
难点:1.初始化的时候由于在第零天的偶数状态dp[0][]都是零所以不用管了,奇数时又都是-prices[0],所以利用j + 2来控制是奇数来进行初始化
2.确定递推公式时也要区分偶数和奇数状态:让j 从1开始(因为状态零代表不进行操作,所以永远是0,不需要进行公式推导),并且利用j += 2,j 和j + 1来控制奇数偶数状态。
3.在遍历数组时for循环里的i和j要起到控制dp数组坐标不越界的作用:最小大于等于0,最大不超过2 * k + 1,由于推导公式里面出现j + 1因此j < 2 * k.
解释细节1:本题的奇数和偶数代表第i天持有和第i天不持有两个状态,由于他们的推倒公式相同,所以可以放在一个循环里面求解,这就是为什么分奇数偶数就能解决本体的原因。
解释细节3:写for的起始值和终止值时要根据数组的范围(一共2 * k + 1个元素)和推导公式的表达式里的最值(j + 1)来确定。
补充:
本题题目是最多进行k次交易,说明也可以不进行交易,这就要求要有状态0即dp[][0]代表不进行交易的最大现金。
题目二链接:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
思路:本题较于买卖股票2区别在于它在卖完股票后会有冰冻期,再由递推公式可以得知还需要有的状态是今天卖出的状态和冰冻期的状态,因此一共有四个状态。另外抓住买卖股票dp的本质:通过前一天的状态推出今天的表达式。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len,vector<int>(4,0));//持有,不持有,今日卖出,冰冻期
dp[0][0] = -prices[0];
for(int i = 1;i < len;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][0] + prices[i]
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
}
return max(dp[len - 1][1],max(dp[len - 1][2],dp[len - 1][3]));
}
};
难点:1.本题有四个状态:多加入了冰冻器和今日卖出的状态
2.确定递推公式时要理解今日卖出的含义:当天卖出,即i - 1那一天卖出
3.最后取状态2,3,4的最大值
4.不持有,今日卖出和冰冻期是三个不同的状态,不可能在同一天同时存在。
题目三链接:714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
思路:与买卖股票二基本相同,只是在二的基础上多了一个手续费,最后在卖出股票产生收益时减去fee即可。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
vector<vector<int>> dp(len,vector<int>(2,0));//dp[i][0]:代表持有股票时所有的钱
//dp数组初始化
dp[0][0] = -prices[0];//每笔交易过程只需要付一次手续费,可以统一在卖出入的时候支付
//确定遍历顺序和递推公式
for(int i = 1;i < len;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[len - 1][1];
}
};
难点:1.**dp[0][1]:第零天不持有可以理解为第零天啥也不干因此就是0**,因为如果理解成不持有股票的意思容易产生减去fee的想法,所以统一一下想法。
2.本题由于在每笔交易过程中只需要支付一次手续费,因此我们只在卖出的时候支付手续费即可。
解释难点1:因此在初始化第0天时,dp[0][1]默认为零