LeetCode第4天 | 动态规划 | 20220716
动态规划
本文章参考了许多他人的笔记,仅供自己学习复习使用。
leetcode官网
Day4目录
还是股票系列
【第一题】 309. 最佳买卖股票时机含冷冻期
1.1 读题
-
已知:
数组prices[0…i], 表示day0-day i 每天的股票价格;
可以多次交易 -
条件:
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)
不能同时参与多笔交易 -
输出:股票收益最大值
首先想到了遍历并用一个变量记录每轮的最大值,在时间复杂度O(n^2)可以求出答案,见1.2.1,但显然会超时。
再考虑如何一次遍历数组得出答案,见1.2.2.
1.2 解题
参考:liweiwei的解答 思路按照liweiwei的,改写了c++代码。以下记录仅供自己学习复习使用。
思路关键:冷冻期
- 卖出股票的当天:不持股;
- 卖出股票的第 2天:冷冻期(不能买入股票,当然也不能卖出股票);
- 卖出股票的第 3天:可以买入股票,也可以什么都不操作。
想到添加一个【状态】来表示冷冻期,但若把「冷冻期」定义成一个状态,不太好推导状态转移方程,由于事实上就只有「持股」和「不持股」这两种情况,因此可以为不持股增加一种情况:
-
第一步:状态定义
dp[i][j]
表示 [0, i] 区间内,在下标为 i 这一天状态为 j 时,我们手上拥有的金钱数。
这里j
可以取 3个值(下面的定义非常重要):
0 表示:今天 不是 卖出了股票的不持股状态;
1 表示:持股;
2 表示:今天由于卖出了股票的不持股状态; -
第二步:推导状态转移方程
由题,从昨天到今天可能的状态变化分析如下:(理解了原答主的思路自己试着列一下,加深理解)
得状态转移图:
-
第三步:思考初始化(第0天)
持股值 = -prices[0];(表示购买了一股)
不持股值 = 0;
冷冻期值 = 0; -
第四步:思考输出
最优值在最后,且最后取不持股值和冷冻期值中的最大值
代码1.1
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <2){
return 0;
}
vector<vector<int>>dp(len, vector<int>(3));
/*思考初始化
不持股值 = 0;
持股值 = -prices[0];(表示购买了一股)
冷冻期值 = 0;
*/
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
// dp[i][0]: 手上不持有股票,并且今天不是由于卖出股票而不持股,我们拥有的现金数
// dp[i][1]: 手上持有股票时,我们拥有的现金数
// dp[i][2]: 手上不持有股票,并且今天是由于卖出股票而不持股,我们拥有的现金数
for(int i = 1; i <len;i++){
// 由状态转移分析得
dp[i][0] = max(dp[i-1][0], dp[i-1][2]);
dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
dp[i][2] = dp[i-1][1]+prices[i];
}
return max(dp[len-1][0],dp[len-1][2]);
}
};
- 第五步:思考空间优化
【因为】当天只与昨天的状态有关,【所以】考虑滚动数组
代码1.2
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <2){
return 0;
}
// 【滚动数组】
vector<vector<int>>dp(2, vector<int>(3));
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for(int i = 1; i <len;i++){
// 【滚动数组】
dp[i%2][0] = max(dp[(i-1)%2][0], dp[(i-1)%2][2]);
dp[i%2][1] = max(dp[(i-1)%2][1],dp[(i-1)%2][0]-prices[i]);
dp[i%2][2] = dp[(i-1)%2][1]+prices[i];
}
return max(dp[(len-1)%2][0],dp[(len-1)%2][2]);
}
};
同样,可以将几个状态分开表示,分别为cash, hold,和freeze。
代码1.3:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <2){
return 0;
}
int cash = 0;
int hold = -prices[0];
int freeze = 0;
int pre0 = cash;
int pre2 = freeze;
for(int i = 1; i <len;i++){
cash = max(cash, pre2);
hold = max(hold, pre0-prices[i]);
freeze = hold+prices[i];
pre0 = cash;
pre2 = freeze;
}
return max(cash,freeze);
}
};
为了程序的正确性,还需要设置两个变量pre0
和pre2
保存上一轮cash和freeze的值
1.3 知识补充 | 二维向量初始化
初始化二维vector,为r*c的vector,所有值为0
vector<vector<int> > newOne(r, vector<int>(c, 0));
【第二题】714.买卖股票的最佳时机含手续费
2.1 读题
2.2 解题
根据买卖股票系列的思路:
第一步:状态定义
第二步:推导状态转移方程
第三步:思考初始化
第四步:思考输出(见代码2.1)
第五步:思考空间优化(见代码2.2)
代码2.1:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
if(len <2){
return 0;
}
// 0:持有现金
// 1:持有股票
vector<vector<int>> dp(len,vector<int>(2));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
// 这两行调换顺序也是可以的
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]-fee);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[len - 1][0];
}
};
空间优化
代码2.2:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
if(len <2){
return 0;
}
// 0:持有现金
// 1:持有股票
vector<vector<int>> dp(2,vector<int>(2));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
// 这两行调换顺序也是可以的
dp[i%2][0] = max(dp[(i-1)%2][0], dp[(i-1)%2][1] + prices[i]-fee);
dp[i%2][1] = max(dp[(i-1)%2][1], dp[(i-1)%2][0] - prices[i]);
}
return dp[(len-1)%2][0];
}
};