Leetcode [121][122][123][188][309][714] 买卖股票的最佳时机i ii iii iv 含冷冻期 含手续费 动态规划
这些题目具有共性,iv是最泛化的题目,所有其他题目都是iv的简化
二、思路:
这个问题的「状态」有三个,第一个是天数,第二个是当天允许交易的最大次数,第三个是当前的持有状态(即之前说的 rest 的状态,我们不妨用 1 表示持有,0 表示没有持有)。
我们用一个三维数组 dp 就可以装下这几种状态的全部组合,用 for 循环就能完成穷举:
而且我们可以用自然语言描述出每一个状态的含义,比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。
想求的最终答案是 dp[n - 1][K][0],即最后一天,最多允许 K 次交易,所能获取的最大利润。读者可能问为什么不是 dp[n - 1][K][1]?因为 [1] 代表手上还持有股票,[0] 表示手上的股票已经卖出去了,很显然后者得到的利润一定大于前者。
状态转移方程:
如果 buy,就要从利润中减去 prices[i],如果 sell,就要给利润增加 prices[i]。今天的最大利润就是这两种可能选择中较大的那个。而且注意 k 的限制,我们在选择 buy 的时候,把最大交易数 k 减小了 1
base case:
把上面的状态转移方程总结一下:
三、解决题目
1、K=1,只能进行一次买卖
根据 base case,可以做一些化简:
class Solution { public: int maxProfit(vector<int>& prices) { int n=prices.size(); vector<vector<int>> dp(n+1,vector<int>(2,0)); dp[0][0]=0; dp[0][1]=INT_MIN; for(int i=1;i<=n;++i){ dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1]); dp[i][1]=max(dp[i-1][1],-prices[i-1]); } return dp[n][0]; } };
2、k = +infinity
如果 k 为正无穷,那么就可以认为 k 和 k - 1 是一样的。可以这样改写框架:
class Solution { public: int maxProfit(vector<int>& prices) { int n=prices.size(); vector<vector<int>> dp(n+1,vector<int>(2,0)); dp[0][0]=0; dp[0][1]=INT_MIN; for(int i=1;i<=n;++i){ dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1]); dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i-1]); } return dp[n][0]; } };
3、k=2
这里要考虑base情况,i=0时和k=0时
PS:这里k代表可以交易的最大次数。遍历中j代表进行了多少次交易
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+prices[i-1]),表示对于最大交易次数为j时,要么就是等于前一天进行交易次数为j时未持有股票的价值,要么等于前一天购买j次交易后持有股票并在i天卖掉的价值
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i-1]),表示对于进行交易次数为j时持有股票的价值,要么等于前一天进行交易次数为j时持有股票的价值,要么等于前一天进行交易次数为j-1(保证之前最大交易次数为j-1,还有1次交易可以留到本次用)未持有股票,然后买了i天股票的价值
class Solution { public: int maxProfit(vector<int>& prices) { int n=prices.size(); int k=2; vector<vector<vector<int>>> dp(n+1,vector<vector<int>>(k+1,vector<int>(2,0))); for(int i=0;i<=k;++i){ dp[0][i][0]=0; dp[0][i][1]=INT_MIN; } for(int i=1;i<=n;++i){ dp[i][0][0]=0; dp[i][0][1]=INT_MIN; } for(int i=1;i<=n;++i){ for(int j=k;j>=1;--j){ dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+prices[i-1]); dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i-1]); } } return dp[n][k][0]; } };
4、就是通用的情况,k也为给定的参数
一次交易由买入和卖出构成,至少需要两天。所以说有效的限制次数 k 应该不超过 n/2,如果超过,就没有约束作用了,相当于 k = +infinity
k=min(k,n/2)
class Solution { public: int maxProfit(int k, vector<int>& prices) { int n=prices.size(); vector<vector<vector<int>>> dp(n+1,vector<vector<int>>(k+1,vector<int>(2,0))); for(int i=0;i<=n;++i){ dp[i][0][0]=0; dp[i][0][1]=INT_MIN; } for(int j=0;j<=k;++j){ dp[0][j][0]=0; dp[0][j][1]=INT_MIN; } for(int i=1;i<=n;++i){ for(int j=1;j<=k;++j){ dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+prices[i-1]); dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i-1]); } } return dp[n][k][0]; } };
5、包含冷冻期,卖出后间隔一天才可以买入
class Solution { public: int maxProfit(vector<int>& prices) { int n=prices.size(); vector<vector<int>> dp(n+1,vector<int>(2,0)); dp[0][0]=0; dp[0][1]=INT_MIN; for(int i=1;i<=n;++i){ if(i==1){ dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1]); dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i-1]); continue; } dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1]); dp[i][1]=max(dp[i-1][1],dp[i-2][0]-prices[i-1]); } return dp[n][0]; } };
6、包含手续费,每买卖一次需要交fee
每次交易要支付手续费,只要把手续费从利润中减去即可:
class Solution { public: int maxProfit(vector<int>& prices, int fee) { int n=prices.size(); vector<vector<long>> dp(n+1,vector<long>(2,0)); dp[0][0]=0; dp[0][1]=INT_MIN; for(int i=1;i<=n;++i){ dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i-1]-fee); dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i-1]); } return (int)dp[n][0]; } };
思路学习自labuladong公众号