参考:https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/tuan-mie-gu-piao-wen-ti
状态:1. 哪一天,2. 允许交易的最大次数,3. 当前的持有状态(持有/没有持有)。
选择:买入,卖出,无操作
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 为最多交易数
此问题共 n × K × 2 种状态,全部穷举就能搞定。
for 0 <= i < n:
for 1 <= k <= K:
for s in {0, 1}:
dp[i][k][s] = max(buy, sell, rest)
///买卖股票 leetcode
base case && 状态转移方程
对某一天而言, 其状态指 buy/sell/rest 操作完成后的状态
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity
状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )
解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )
解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
状态描述中的 k 表示:最多允许 k 次交易。所以说,如果我昨天没有持有,但是我今天要 buy,那么昨天最多允许 k-1 次交易。
///买卖股票1 leetcode 121
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]
// dp[i][1] = max(dp[i-1][1], - prices[i])
int maxProfit_k_1(vector<int>& prices) {
///directly
// int n = prices.size();
// if(n == 0) return 0;
// 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;
// }
// 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];
///当前状态只与相邻状态有关, 可以降低空间复杂度到O(1)
int n = prices.size();
if(n == 0) return 0;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0; // is dp[-1][0]
int dp_i_1 = INT_MIN; //include <climits>; it is dp[-1][1]
for(int i = 0; i < n; i++) {
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
// dp[i][1] = max(dp[i-1][1], - prices[i]);
dp_i_1 = max(dp_i_1, - prices[i]);
}
return dp_i_0;
}
///买卖股票2 leetcode 122
// k无穷大, k = k-1, 也就是说不需要记录 k 这个状态了:
//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])
int maxProfit_k_inf(vector<int>& prices) {
int n = prices.size();
if(n == 0) return 0;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0; // is dp[-1][0]
int dp_i_1 = INT_MIN; //include <climits>; it is dp[-1][1]
for(int i = 0; i < n; i++) {
int temp = dp_i_0; //dp_i_0会改变!
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
///买卖股票 有冷冻期 leetcode 309
//对某一天而言, 其状态指 buy/sell/rest 操作完成后的状态
//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])
//解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1
//在第i天buy, 不可能在 i-1 天sell, 所以 i-1 天铁定没有收益
int maxProfit_cool(vector<int>& prices) {
int n = prices.size();
if(n == 0) return 0;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0; // dp[i-1][0], i is 0
int dp_i_1 = INT_MIN; //include <climits>; dp[i-1][1], i is 0
int pre = 0; // dp[i-2][0]
for(int i = 0; i < n; i++) {
int temp = dp_i_0; //dp_i_0会改变!
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, pre - prices[i]);
pre = temp;
}
return dp_i_0;
}
///买卖股票 有手续费 leetcode 714
//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)
int maxProfit_fee(vector<int>& prices, int fee) {
int n = prices.size();
if(n == 0) return 0;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0; // is dp[-1][0]
int dp_i_1 = INT_MIN; //include <climits>; it is dp[-1][1]
for(int i = 0; i < n; i++) {
int temp = dp_i_0; //dp_i_0会改变!
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, temp - prices[i] - fee);
}
return dp_i_0;
}
///买卖股票3 leetcode 123, k = 2
//dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
//dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
int maxProfit_k_2(vector<int>& prices) {
int n = prices.size();
if(n == 0) return 0;
int max_k = 2;
vector<vector<vector<int>>> dp(n, vector<vector<int>>(max_k+1, vector<int>(2)));
//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 k = 1; k <= 2; k++) {
if(i - 1 == -1) { // base case
dp[i][k][0] = 0;
dp[i][k][1] = -prices[i];
continue;
}
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);
}
}
return dp[n-1][max_k][0];
}
///买卖股票 k有值 leetcode 188
int maxProfit_k_any(vector<int>& prices, int k) {
int n = prices.size();
if(n == 0) return 0;
int max_k = k;
vector<vector<vector<int>>> dp(n, vector<vector<int>>(max_k+1, vector<int>(2)));
//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 k = 1; k <= max_k; k++) {
if(i - 1 == -1) { // base case
dp[i][k][0] = 0;
dp[i][k][1] = -prices[i];
continue;
}
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);
}
}
return dp[n-1][max_k][0];
}
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if(k > n / 2) // k is inf
return maxProfit_k_inf(prices);
return maxProfit_k_any(prices, k);
}