动态规划问题主要就是要明确dp函数定义、搞清楚状态以及状态转移方程
构建DP思路解析
状态
188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
对于股票,我们每天有三种选择 => buy, sell, hold
限制条件有 => 天数限制(n),买入股票次数限制(k)
状态(动态过程) => [第 i 天][还剩下 j 次买入机会][有无股票]
dp[i][k][0 or 1] 表示收益
0 <= i < n, 1 <= j <= k
n 为天数,大 K 为买入数的上限,0 和 1 代表是否持有股票。
此问题共 n × k × 2 种状态,全部穷举就能搞定。for 0 <= i < n:
for 1 <= j <= k:
for s in {0, 1}:
dp[i][j][s] = max(buy, sell, rest)
最终所求状态 => dp[ n ][ k ][ 0 ] (最后不把股票抛出去不就是全亏嘛)
状态转移方程
根据每天的三种选择(buy, sell, hold)
=> 两种状态转移:
1. dp[ i ][ j ][0](不持有股票) = max(hold, sell) = max(dp[i - 1][j][0], dp[i - 1][j][1] + price[i] );
2. dp[ i ][ j ][1] (持有股票) = max(hold, buy) = max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - price[i]);
考虑base case
1. 从天数角度 => 下标从0开始表示从第一天开始 => 和题目给的prices数组保持一致
(这里相当于把第一天的两种情况穷举出来了)
dp[0][...][0] = 0;
dp[0][...][1] = -prices[0];
2. 从交易次数角度 => k = 0 表示不允许买股票,当然不可能持有股票 => NAN
dp[...][0][0] = 0;
dp[...][0][1] = NAN;(可以赋值INT_MIN方便max())
LeetCode实战
仅有一次买入机会
dp[i][1] = max(dp[i-1][1], -prices[i]); => 在选择在哪一天买入,在此之前没买过
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = 0, dp[0][1] = -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], -prices[i]);
}
return dp[n-1][0];
}
};
马尔可夫链 => 空间压缩
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp_0 = 0, dp_1 = -prices[0];
for(int i = 1; i < n; i++) {
dp_0 = max(dp_0, dp_1 + prices[i]);
dp_1 = max(dp_1, -prices[i]);
}
return dp_0;
}
};
122. 买卖股票的最佳时机 II - 力扣(LeetCode)
无限次买入机会
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = 0, dp[0][1] = -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]);
}
return dp[n-1][0];
}
};
空间压缩
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp_0 = 0, dp_1 = -prices[0];
for(int i = 1; i < n; i++) {
int temp = dp_0;
dp_0 = max(dp_0, dp_1 + prices[i]);
dp_1 = max(dp_1, temp - prices[i]);
}
return dp_0;
}
};
123. 买卖股票的最佳时机 III - 力扣(LeetCode)
k = 2;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<vector<int>>>dp (n, vector<vector<int>>(3, vector<int>(2)));
for(int i = 0; i < n; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = INT_MIN;
}
for(int i = 0; i < n; i++) {
for(int j = 2; j > 0; j--) {
if(i == 0) {
dp[i][j][0] = 0;
dp[i][j][1] = -prices[i];
continue;
}
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
}
}
return dp[n-1][2][0];
}
};
空间压缩
先迭代k == 2的情况不会影响后面k == 1的迭代
class Solution {
public:
int maxProfit(vector<int>& prices) {
// base case;
int dp_10 = 0, dp_11 = -prices[0];
int dp_20 = 0, dp_21 = -prices[0];
for(int price: prices) {
dp_20 = max(dp_20, dp_21 + price);
dp_21 = max(dp_21, dp_10 - price);
dp_10 = max(dp_10, dp_11 + price);
dp_11 = max(dp_11, -price);
}
return dp_20;
}
};
188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if(n <= 0) return 0;
// 细分一下,可以认为退化为k无限的情况
if(k > n/2) return InfinityK(prices);
vector<vector<vector<int>>> dp(n, vector<vector<int>>(k + 1, vector<int>(2)));
// k = 0 的 base case
for(int i = 0; i < n; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = INT_MIN;
}
for(int i = 0; i < n; i++) {
for(int j = k; j > 0; j--) {
// i == 0 的 base case
if(i == 0) {
dp[i][j][0] = 0;
dp[i][j][1] = -prices[i];
continue;
}
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
}
}
return dp[n-1][k][0];
}
int InfinityK(vector<int>& prices) {
int n = prices.size();
int dp_0 = 0, dp_1 = -prices[0];
for(int i = 1; i < n; i++) {
int temp = dp_0;
dp_0 = max(dp_0, dp_1 + prices[i]);
dp_1 = max(dp_1, temp - prices[i]);
}
return dp_0;
}
};
其他变式
309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天) => 修改状态转移方程
=> 持有股票:
dp[ i ][ j ][1] = max(hold, buy) = max(dp[i - 1][j][1], dp[i - 2][j - 1][0] - price[i]);
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
for(int i = 0; i < n; i++) {
if(i - 1 == -1) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
if(i - 2 == -1) {
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = max(dp[i-1][1], -prices[i]);
continue;
}
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]);
}
return dp[n-1][0];
}
};
714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
有手续费不就相当于买股票总费用上升了么
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
for(int i = 0; i < n; i++) {
if(i - 1 == -1) {
dp[i][0] = 0;
dp[i][1] = -prices[i] - fee;
continue;
}
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[n-1][0];
}
};