188.买卖股票的最佳时机IV
文章链接:188.买卖股票的最佳时机IV
视频链接:动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4
状态:其实和题目123.买卖股票的最佳时机III非常类似,搞懂上一题,这一题就不算难题
本题其实和123.买卖股票的最佳时机III很像样,在123题中,我们只能买卖两次,但是本题却能买卖k次!这样我们又应该如何处理呢?
思路
- 确定dp数组以及下标的含义
仍然使用一个二维dp[i][j]
:第i天的状态为j,所剩下的最大现金是dp[i][j]
j的状态表示为:
- 0 表示不操作
- 1 第一次买入
- 2 第一次卖出
- 3 第二次买入
- 4 第二次卖出
- …
除了0以外,偶数就是卖出,奇数就是买入。
题目要求是至多有K笔交易,那么那么j的范围就定义为 2 * k + 1 就可以了。
vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
- 确定递推公式
跟之前的三道题都是老样子(一定要做买卖股票的最佳时机I~III这里的三道题)。
达到dp[i][1]
状态,有两个具体操作:
- 操作一:第
i
天买入股票了,那么dp[i][1]
=dp[i - 1][0] - prices[i]
- 操作二:第
i
天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
选最大的,所以 dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])
同理dp[i][2]
也有两个操作:
- 操作一:第
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][1] + prices[i], dp[i - 1][2])
剩下的dp[i][2+1]
、dp[i][2+2]
也就是我们的第二次买入、第二次卖出的情况以此类推,就有
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]);
}
- dp数组的初始化
我们的每一天状态都依赖于前一天的状态,所以我们只初始化dp[0][j]
再一个,我们要初始化第0天的每一次买入卖出操作,
其实很直观我们每次买入肯定都是-price[0]
,每一次在同一天买入卖出肯定都剩0
元,所以:
for (int j = 1; j < 2 * k; j += 2) {
dp[0][j] = -prices[0];
}
在初始化的地方同样要类比j为偶数是卖、奇数是买的状态。
- 确定遍历顺序
跟以前一样,从前向后
- 举例推导dp数组
以输入[1,2,3,4,5],k=2为例。并且最后一天的最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]
即红色部分就是最后求解。
CPP代码
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));
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][2 * k];
}
};
//滚动数组
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len = prices.size();
vector<int> dp(2 * k + 1, 0);
for (int i = 2; i <= 2 * k; i += 2) {
dp[i - 1] -= prices[0];
}
for (int i = 1; i < len; i++) {
for (int j = 1; j <= 2 * k; j++) {
dp[j] = max(dp[j], dp[j - 1] + (j % 2 == 0 ? prices[i] : -prices[i]));
}
}
return dp[2 * k];
}
};
⭐️309.最佳买卖股票时机含冷冻期
文章讲解:309.最佳买卖股票时机含冷冻期
视频讲解:动态规划来决定最佳时机,这次有冷冻期!| LeetCode:309.买卖股票的最佳时机含冷冻期
状态:在思考的过程中,要学会分化某些情况的递推公式。本题明显需要把
本题和之前又有什么区别呢?本题加上了一个冷冻期,也就是我们在卖出股票后,第二天不能再买入,必须得隔一天
思路
- 确定dp数组以及下标的含义
跟之前一样dp[i][j]
表示第i天状态为j,所剩下的最多现金为dp[i][j]
首先可以有一个粗略的状态定义:今天买入、今天卖出、今天是冷冻期
本题的状态有如下四种:
状态一:持有股票(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
状态二:不持有股票
- **状态2.1:**不持有股票,但是保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
- **状态2.2:**今天卖出股票
状态三:今天为冷冻期,但是仅限今天!
综上所述,j
的状态为:
0:状态1
1:状态2.1
2:状态2.2
3:状态3
- 确定递推公式
现在递推公式其实就直接分状态推理,比较简单:
持有股票状态(状态一)即:dp[i][0]
,有两个具体操作:
- 操作一:前一天就是持有股票状态(状态一),
dp[i][0] = dp[i - 1][0]
- 操作二:今天买入了,有两种情况
- 前一天是冷冻期(状态四),
dp[i - 1][3] - prices[i]
- 前一天是保持卖出股票的状态(状态2.1),
dp[i - 1][1] - prices[i]
- 前一天是冷冻期(状态四),
所以,本状态递推公式:
d
p
[
i
]
[
0
]
=
m
a
x
(
d
p
[
i
−
1
]
[
0
]
,
d
p
[
i
−
1
]
[
3
]
−
p
r
i
c
e
s
[
i
]
,
d
p
[
i
−
1
]
[
1
]
−
p
r
i
c
e
s
[
i
]
)
dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i])
dp[i][0]=max(dp[i−1][0],dp[i−1][3]−prices[i],dp[i−1][1]−prices[i])
需要注意的是max只接受两个传参。
达到保持卖出股票状态二(状态2.1)即:dp[i][1]
,有两个具体操作:
- 操作一:前一天就是卖股票状态(状态2.1):
dp[i-1][1]
- 操作二:前一天是冷冻期(状态3):
dp[i-1][3]
所以,本状态递推公式:dp[i][1] = max(dp[i - 1][1], dp[i - 1][3])
达到今天就卖出股票状态二(状态2.2),此时昨天一定是持有股票的状态,所以今天才能卖出,所以只有一个操作
dp[i][2] = dp[i - 1][0] + prices[i]
达到冷冻期状态(状态四),即:dp[i][3]
,这样的话昨天一定把股票卖了,所以只有一个操作:
dp[i][3] = dp[i - 1][2];
综上所述,递推代码如下:
dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], 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];
- dp数组如何初始化
还是比较直观的, 也就是dp[0][0]=-prices[0]
状态二状态三为了不影响第二天的递推,均初始化为0.
- 确定遍历顺序
老样子 ,从前向后
- 举例推导dp数组
以 [1,2,3,0,2] 为例,dp数组如下:
CPP代码
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0;
vector<vector<int>> dp(n, vector<int>(4, 0));
dp[0][0] -= prices[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]));
}
}
714.买卖股票的最佳时机含手续费
文章链接:714.买卖股票的最佳时机含手续费
手续费来喽!本题中也可以进行无线次买卖,但是每次交易都必须支付手续费,题目中指明买卖一次,只叫一笔手续费,那么我们定义每次买入不交,卖出交手续费。
所以其实本题与动态规划:122.买卖股票的最佳时机II其实是一样一样的。只需要在计算卖出操作的时候减去手续费就可以了
思路
这里只对递推公式的不同点进行讲解,因为其他地方与动态规划:122.买卖股票的最佳时机II一摸一样。
首先明确:dp[i][0]
表示第i
天持有股票所省最多现金。dp[i][1]
表示第i
天不持有股票所得最多现金
如果我们在第i
天持有股票,即dp[i][0]
,所有只有两种状态,第i-1天已经持有和第i天买入,这两种状态我们都不用交手续费
如果在第i
天不持有股票,即dp[i][1]
,也只有两种情况,第i-1天就不持有股票和第i天卖出股票,此时我们需要对第二种状态交手续费,也就是直接减去手续费即可dp[i - 1][0] + prices[i] - fee
总体代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] -= prices[0]; // 持股票
for (int i = 1; i < n; 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 max(dp[n - 1][0], dp[n - 1][1]);
}
};