总结:
- 前四道都可以用二维DP,定义状态第一维代表当前天数,第二维代表当天持有股票和不持有股票两种状态,dp[ i ] [ 0 ] 代表第 i + 1 天(下标是从0开始) 不持有股票的状态,dp[ i ] [ 1 ] 表示第 i + 1 天持有股票的状态时收益最大。
- 状态转移,当天不持有股票可以从前一天就不持有的状态转移过来,也可以是前一天持有但是现在卖出的状态转移过来,当天持有股票的状态可以从前一天就持有状态转移,也可以是前一天不持有股票但是现在买入的状态转移过来。
- 求最大收益,就取以上状态中的最大值即可,最大收益在最后一天的卖出状态取得
121. 买卖股票的最佳时机(一次交易)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2,0));
//初始化baseCase,第1天不持有股票的最大收益为0,第1天持有股票的最大收益(在第一天买入)
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]); //只允许一次交易,之前的交易收益视为0
}
return dp[n-1][0];
}
};
122. 买卖股票的最佳时机2 (多次交易)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len,vector<int>(2,0));
dp[0][0] = 0, dp[0][1] = -prices[0];
for(int i = 1;i < len;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[len-1][0];
}
};
309. 最佳买卖股票时机含冷冻期(在上一次交易后需要等待一天才能购入)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n < 2){ //因为初始化前两天的状态,所以需要考虑prices长度小于2
return 0;
}
int dp[n][2]; //0 - 不持有股票,1 - 持有股票
//初始化前两天的baseCase
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = max(dp[0][0], dp[0][1] + prices[1]);
dp[1][1] = max(dp[0][1], -prices[1]); //第二天购入只能是第一天购入或者第二天购入
for(int i = 2;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-2][0] - prices[i]);
}
return dp[n-1][0];
}
};
714. 买卖股票的最佳时机含手续费
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int dp[n][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] - fee);//一次交易减去手续费
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
}
return dp[n-1][0];
}
};
后面两题限制交易次数,所以需要再加一维表示次数,但是对于baseCase初始化边界暂时还不是很理解,这里只记录了自己认为比较好理解的方法
123. 买卖股票的最佳时机3(限制交易两次)
思路:考虑交易两次所有的状态,使用变量记录
-
交易两次一共有5种状态,不操作(free)、第一次买入(buy1)、第一次卖出(sell1)、第二次买入(buy2)、第二次卖出(sell2),但是由于不操作对于收益为0,所以可以简单考虑后面四种状态
-
将 i - 1天的这四种状态,如何转移到第i天,
buy1_i : 第 i - 1 天第一次买入(buy1_i-1)之后没有操作, 或者第 i 天 才第一次买入
sell1_ i : 第 i - 1 天第一次卖出(sell1_i-1)之后没有操作, 或者第 i - 1 天第一次买入第 i 天第一次卖出
buy2_i : 第 i - 1天第二次买入(buy2_i-1)之后没有操作, 或者第 i - 1天第一次卖出后第 i 天第二次买入
sell2_i : 第 i - 1 天第二次卖出(sell2_i-1)之后没有操作, 或者第 i - 1天第二次买入后第 i 天第二次卖出
-
最大收益由于始终维护的是最大值,并且同一天买入和卖出不影响收益这一宽松条件使得,sell2处获得的是最终的最大收益
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int buy1 = -prices[0];
int sell1 = 0;
int buy2 = -prices[0];
int sell2 = 0;
for(int i = 0;i < n;i++){
buy1 = max(buy1, -prices[i]);
sell1 = max(sell1, buy1 + prices[i]);
buy2 = max(buy2, sell1 - prices[i]);
sell2 = max(sell2, buy2 + prices[i]);
}
return sell2;
}
};
188. 买卖股票的最佳时机4(限制K次交易)
思路:考虑k次交易可能存在的所有状态
-
进行k次交易总共存在2*k + 1次状态(无操作,第k次买入,第k次卖出 = 1 + k + k)
-
将状态分为两大类(买入和卖出),[1 - 2*k]中 奇数次代表买入,偶数次代表卖出,初始化baseCase,第一天的状态中买入操作初始化为 -prices[0],卖出操作和不操作收益都为0
-
第 i 天的状态由第 i - 1天的状态转移而来,
第 i 天买入可以是第 i - 1 天的买入(不操作) 或者 第 i - 1 天的卖出后买入
第 i 天卖出可以是第 i - 1 天卖出(不操作) 或者 第 i - 1天买入后卖出
-
dp中偶数次维护的是截至到第 i 天 第 k 次交易(卖出)最大收益,最终最大收益 = dp[2 * k]
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
k = min(k, n/2); //prices天数不够K次交易的情况,取k 和 n/2 中的最小值
vector<int> dp(2*k+1,0); //0代表不操作,奇数代表买,偶数代表卖
//初始化第1天状态的baseCase
for(int i = 1;i < 2*k+1;i++){
if(i % 2){
dp[i] = -prices[0]; //第1天的所有奇数次状态买入,偶数次卖出收益0
}
}
for(int i = 1;i < n;i++){
for(int j = 1;j < 2*k+1;j++){
if(j % 2 != 0){
dp[j] = max(dp[j], dp[j-1] - prices[i]); //买入
}else{
dp[j] = max(dp[j], dp[j-1] + prices[i]); //卖出
}
}
}
return dp[2*k];
}
};