目录
股票题
前言
此题是k = 2
利用「状态」进行穷举。我们具体到每一天,看看总共有几种可能的「状态」,再找出每个「状态」对应的「选择. 我们要穷举所有「状态」,穷举的目的是根据对应的「选择」更新状态
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 择优(选择1,选择2...)
每天都有三种「选择」:买入、卖出、无操作,我们用 buy, sell, rest 表示这三种选择
并不是每天都可以任意选择这三种选择的,因为 sell 必须在 buy 之后,buy 必须在 sell 之后
rest 操作还应该分两种状态,一种是 buy 之后的 rest(持有了股票),一种是 sell 之后的 rest(没有持有股票)
我们还有交易次数 k 的限制,就是说你 buy 还只能在 k > 0 的前提下操作
dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。很容易理解,对吧?
状态转移框架
状态转移方程:
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,所以今天我就持有股票了。
如果 buy,就要从利润中减去 prices[i],如果 sell,就要给利润增加 prices[i]
定义base case
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。
总结
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])
121. 买卖股票的最佳时机
思路
此时K == 1 可以先忽略不计
dp[i][0]:表示第i天没有持有股票 : 1.保持前一天没持有; 2.昨天持有,但是卖了
dp[i][1]:表示第i天有持有 : 1.他保持前一天持有; 2.今天买了
int maxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
int dp[prices.size()][2];
for (int i = 0; i < prices.size(); i++) {
if (i - 1 == -1) {
dp[i][1] = -prices[i];
dp[i][0] = 0;
continue;
}
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//因为只买卖一次 K=1的情况下, dp[i][1] 不需要考虑当前余额,如果需要就要dp[i - 1][0] -prices[i]
dp[i][1] = max(dp[i - 1][1], -prices[i]);
}
return dp[prices.size() - 1][0];
}
122.买卖股票的最佳时机II
思路
与上题相比, k是无限,可以多次交易
int maxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
int dp[prices.size()][2];
for (int i = 0; i < prices.size(); i++) {
if (i - 1 == -1) {
dp[0][0] = 0;
dp[0][1] = -prices[i];
continue;
}
//因为是多次交易,所以需要保存temp, temp是昨天为持有的收益,可能是已经卖了
int temp = dp[i - 1][0];
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], temp - prices[i]);
}
return dp[prices.size() - 1][0];
}
714. 买卖股票的最佳时机含手续费
思路
就是在卖出的时候加上手续费就行
int maxProfit(vector<int>& prices, int fee) {
if (prices.size() < 2) return 0;
int dp[prices.size()][2];
for (int i = 0; i < prices.size(); i++) {
if (i == 0) {
dp[0][0] = 0;
dp[0][1] = -prices[0];
continue;
}
int temp = dp[i - 1][0];
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = max(dp[i - 1][1], temp - prices[i]);
}
return dp[prices.size() - 1][0];
}
309. 最佳买卖股票时机含冷冻期
思路
就是买的话是从[i - 2] 开始
//f1 因为可能冻结期有2 3 4天, 此方法不靠谱
int maxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
int dp[prices.size()][2];
for (int i = 0; i < prices.size(); i++) {
if (i == 0) {
dp[0][0] = 0;
dp[0][1] = -prices[0];
continue;
}
if (i == 1) {
dp[1][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[1][1] = max(dp[i - 1][1], dp[i - 1][0] - 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[prices.size() - 1][0];
}
//f2
int maxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
//前两天未持有
int pre2_i_0 = 0;
//第i天未持有
int dp_i_0 = 0;
//第i天持有
int dp_i_1 = INT_MIN;
for (int i = 0; i < prices.size(); i++) {
int temp = dp_i_0;
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, pre2_i_0 - prices[i]);
pre2_i_0 = temp;
}
return dp_i_0;
}
123. 买卖股票的最佳时机 III
思路
K == 2了, 需要创建一个三维的dp
int maxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
int max_k = 2;
int dp[prices.size()][max_k + 1][2];
//初始化这块内存区域,全部设置为0
memset(dp, 0, sizeof(dp));
for (int i = 0; i < prices.size(); i++) {
for (int k = max_k; k >= 1; k--) {
if (i - 1 == -1) {
dp[0][k][0] = 0;
dp[0][k][1] = -prices[0];
continue;
}
//dp[i][k][0] 第i天还有k次交易机会没持有: 没持有原因是 保持昨天或者昨天买今天卖
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[prices.size() - 1][max_k][0];
}
188. 买卖股票的最佳时机 IV
思路
和上题k = 2 一样, 只不过这次k > 2, 那么因为股票买进卖出最多是 prices.size / 2次, 大于的话就相当于k=无限
那么就可以忽略k了
//k为不限制的function
int moewMaxProfit(vector<int>& prices) {
if (prices.size() < 2) return 0;
int dp[prices.size()][2];
for (int i = 0; i < prices.size(); i++) {
if (i - 1 == -1) {
dp[0][0] = 0;
dp[0][1] = -prices[i];
continue;
}
//因为是多次交易,所以需要保存temp, temp是昨天为持有的收益,可能是已经卖了
int temp = dp[i - 1][0];
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], temp - prices[i]);
}
return dp[prices.size() - 1][0];
}
int maxProfit(int k, vector<int>& prices) {
if (prices.size() < 2) return 0;
//如果k > 一半的话就是无限的
if (k > prices.size() / 2) {
return moewMaxProfit(prices);
}
int max_k = k;
int dp[prices.size()][max_k + 1][2];
memset(dp, 0, sizeof(dp));
for (int i = 0; i < prices.size(); i++) {
for (int k = max_k; k >= 1; k--) {
if (i - 1 == -1) {
dp[0][k][0] = 0;
dp[0][k][1] = -prices[0];
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[prices.size() - 1][max_k][0];
}