参考labuladong的解析:https://labuladong.gitee.io/algo/1/13/
一、穷举框架
这 6 道题目是有共性的,我们只需要抽出来力扣第 188 题「 买卖股票的最佳时机 IV」进行研究,因为这道题是最泛化的形式,其他的问题都是这个形式的简化,看下题目:
动态规划核心套路 说过,动态规划算法本质上就是穷举「状态」,然后在「选择」中选择最优解。
那么对于这道题,我们具体到每一天,看看总共有几种可能的「状态」,再找出每个「状态」对应的「选择」。我们要穷举所有「状态」,穷举的目的是根据对应的「选择」更新状态。
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 择优(选择1,选择2...)
dp[i][k][0 or 1]
0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,0 和 1 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。
for 0 <= i < n:
for 1 <= k <= K:
for s in {0, 1}:
dp[i][k][s] = max(buy, sell, rest)
而且我们可以用自然语言描述出每一个状态的含义,比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。很容易理解,对吧?
我们想求的最终答案是 dp[n - 1][K][0],即最后一天,最多允许 K 次交易,最多获得多少利润。
二、状态转移框架
现在,我们完成了「状态」的穷举,我们开始思考每种「状态」有哪些「选择」,应该如何更新「状态」。只看「持有状态」,可以画个状态转移图:
通过这个图可以很清楚地看到,每种状态(0 和 1)是如何转移而来的。根据这个图,我们来写一下状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 今天选择 rest, 今天选择 sell )
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 今天选择 rest, 今天选择 buy )
边界:
dp[-1][...][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。
dp[-1][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。
dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
三、各个题目分析
------------属于k=1的情况------------
状态分析:由于最多只有一笔交易,所以第二个状态的取值只有1,所以第二个状态去掉了,且k = 0为边界,都是0
所以状态转移方程为:
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + v[i])
dp[i][1] = max(dp[i - 1][1], 0 - v[i])
-------------------------------------------
int maxProfit(vector<int>& v) {
//初始化dp[0][1]为-price[0]
int a = 0, b = -v[0], n = v.size();
for (int i = 1; i < n; ++i) {
int c = max(a, b + v[i]);
int d = max(b, -v[i]);
a = c;
b = d;
}
return a;
}
-----------属于k为正无穷的情况-------------
k没有存在的意义,所以把第二个状态省略掉
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + v[i])
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - v[i]);
------------------------------------
int maxProfit(vector<int>& v) {
int a = 0, b = INT_MIN, n = v.size();
for (int i = 0; i < n; ++i) {
int c = max(a, b + v[i]);
int d = max(b, a - v[i]);
a = c;
b = d;
}
return a;
}
------------包含冷冻期的情况,leetcode122的变种题----------
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + v[i])
dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - v[i]);
-----------------------------------------
int maxProfit(vector<int>& v) {
int n = v.size();
vector<vector<int>> dp(n + 1, vector<int>(2));
dp[0][0] = 0; dp[0][1] = INT_MIN;
for (int i = 0; i < n; ++i) {
dp[i + 1][0] = max(dp[i][0], dp[i][1] + v[i]);
//dp[-2][0]为0,此时单独考虑
if (i - 1 >= 0) dp[i + 1][1] = max(dp[i][1], dp[i - 1][0] - v[i]);
else dp[i + 1][1] = max(dp[i][1], -v[i]);
}
return dp[n][0];
}
------------包含手续费的情况,leetcode122的变种题----------
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + v[i])
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - v[i] - fee)
---------------------------------------
int maxProfit(vector<int>& v, int fee) {
int a = 0, b = INT_MIN, n = v.size();
for (int i = 0; i < n; ++i) {
int c = max(a, b + v[i]);
int d = max(b, a - v[i] - fee);
a = c;
b = d;
}
return a;
}
------------k=2,就是之前的模板,k的取值为1和2-------------
边界:
dp[-1][][0]=0 dp[-1][][1]=无穷小
dp[i][0][0]=0 dp[i][0][1]=无穷小
转移方程:
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + v[i])
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - v[i])
----------------------------------------------
int maxProfit(vector<int>& v) {
int n = v.size();
int dp[n + 1][3][2];
//i==-1的base:
for (int i = 0; i < 3; ++i) {
dp[0][i][0] = 0;
dp[0][i][1] = INT_MIN;
}
//k==0的base:
for (int i = 0; i < n; ++i) {
dp[i + 1][0][0] = 0;
dp[i + 1][0][1] = INT_MIN;
}
for (int i = 0; i < n; ++i) {
for (int k = 1; k < 3; ++k) {
dp[i + 1][k][0] = max(dp[i][k][0], dp[i][k][1] + v[i]);
dp[i + 1][k][1] = max(dp[i][k][1], dp[i][k - 1][0] - v[i]);
}
}
return dp[n][2][0];
}
------------k=n,就是之前的模板,k的取值为1->n-------------
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + v[i])
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - v[i])
------------------------------------------------------------
int maxProfit(int k, vector<int>& v) {
int n = v.size();
int dp[n + 1][k + 1][2];
for (int i = 0; i < k + 1; ++i) {
dp[0][i][0] = 0;
dp[0][i][1] = INT_MIN;
}
for (int i = 0; i < n; ++i) {
dp[i + 1][0][0] = 0;
dp[i + 1][0][1] = INT_MIN;
}
for (int i = 0; i < n; ++i) {
for (int j = 1; j < k + 1; ++j) {
dp[i + 1][j][0] = max(dp[i][j][0], dp[i][j][1] + v[i]);
dp[i + 1][j][1] = max(dp[i][j][1], dp[i][j - 1][0] - v[i]);
}
}
return dp[n][k][0];
}