今天在leetcode上刷题时,遇到了这个题目,看到有一个题解写的非常好,提出一种方法能够解决全部的买卖股票的相关问题(在leetcode中分别为121,122,123,188,309,714),现将其整理出来,并加入一些自己的理解。文章参考自:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
方法介绍
首先,我们要明确 买+卖为一次交易
定义状态dp[i][k][0 or 1],表示在第i天,至今最多进行j次交易并且手中持有or没有股票所能获得的最大收益,其中,0<=i<=n-1, 1<=j<=K(K为最多交易次数)。
例如dp[3][2][1]的含义就是:在第3天,至今最多进行2次交易手中持有股票所能获得的最大收益。
dp[2][3][0]的含义就是:在第2天,至今最多进行3次交易,手中没有持有股票所能获得的最大收益。
状态转移方程
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
//解释:今天我没有持有股票,有两种可能
//1、要么是我昨天就没有持有,今天选择了rest,所以我今天还是没有持有
//2、要么时我昨天持有股票,但是今天选择了卖出,所以我今天没有持有
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
//解释:今天我持有股票,有两种可能
//1、要么是我昨天就持有,今天选择了rest,所以我今天还是持有
//2、要么时我昨天没有持有股票,但是今天选择了买入,所以我今天持有
在这个地方,我当时存在一些疑问,为什么第一个式子不是
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k-1][1]+prices[i]);
后来通过评论区的评论,再结合我自己的理解,想出了这样一种解释。
首先,要明确题目中定义的时进行一次完成的买卖操作为一次交易,所以,你可以定义先买后卖为一次交易,也可以定义先卖后买为一次交易,两种定义选其一,因为一次交易只能被记录一次。如果选择先买后卖,那么就是当进行买入操作时即开启了一次新的交易,也就是上面的状态转移方程,即
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][1]+prices[i]);//卖
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k][0]-prices[i]);//买
但是,如果采用此种定义方法,意味着你在一开始进行的时卖出操作,但是在手上没有股票的时候,是无法进行卖出操作的,所以定义方式在leetcode上无法通过
初始化
dp[-1][k][0] = 0;//i从0开始,当i=-1时,因为这交易还没有开始,最大利润为0
dp[-1][k][1] = -INT_MAX;//交易开始,手中不可能持有股票
dp[i][0][0] = 0;//允许最大交易次数为0,最大利润为0
dp[i][0][1] = -INT_MAX;//允许最大交易次数为0.手中不可能持有股票
解题
方法介绍完了,接下来就用着这方法解决6道股票买卖问题。
121-只允许完成一次交易,k=1
分析
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
dp[i][1][0] = max(dp[i-1][1][0],dp[i-1][1][1]+prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买
dp[i][1][1] = max(dp[i-1][1][1],dp[i-1][0][0]-prices[i])
= max(dp[i-1][1][1],-prices[i])
//可以发现k都是1,不会改变,即k对状态转移已经没有影响了
代码实现
int maxProfit(vector<int>& prices) {
if(prices.size() == 0)
return 0;
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]);
dp[i][1] = max(dp[i-1][1],-prices[i]);
}
return dp[n-1][0];
}
122-无交易次数限制,k=infinity
分析
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
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][1] = max(dp[i-1][1][1],dp[i-1][k][0]-prices[i])
= max(dp[i-1][1][1],dp[i-1][k][0]-prices[i])
//可以发现k由于为无限大,所以k-1和k没有区别,即k对状态转移已经没有影响了
代码实现
int maxProfit(vector<int>& prices) {
if(prices.size() == 0)
return 0;
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]);
dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[n-1][0];
}
123-限制2次交易,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]);//买
//初始化
dp[0][k][0] = 0//第0天,至今最多交易k次,目前手中没有股票,所以最大利润为0
dp[0][k][1] = -prices[0]//第0天,至今最多交易k次,目前手中有股票,即最大利润为-prices[0]
dp[i][0][0] = 0//第0天,至今最多交易0次,目前手中没有股票,所以最大利润为0
dp[i][0][1] = -INT_MAX//第0天,至今最多交易0次,目前手中有股票,不可能发生,用infinity代表
代码实现
int maxProfit(vector<int>& prices) {
if(prices.size() == 0)
return 0;
int n = prices.size();
int dp[n][3][2];
for(int i=0;i<n;i++){
dp[i][0][0] = 0;
dp[i][0][1] = -INT_MAX;
for(int k=2;k>0;k--){
if(i == 0){
dp[i][k][0] = 0;
dp[i][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[n-1][2][0];
}
188-限制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]);//买
//初始化
dp[0][k][0] = 0//第0天,至今最多交易k次,目前手中没有股票,所以最大利润为0
dp[0][k][1] = -prices[0]//第0天,至今最多交易k次,目前手中有股票,即最大利润为-prices[0]
dp[i][0][0] = 0//第0天,至今最多交易0次,目前手中没有股票,所以最大利润为0
dp[i][0][1] = -INT_MAX//第0天,至今最多交易0次,目前手中有股票,不可能发生,用infinity代表
另外,当k值过大时,会使k的那层循环趋向于无穷大,但是我们通过分析,因为买+卖为完整的一次交易,当k>prices.size()/2,实际相当于对于k的限制消失,即k为无穷的那种情况
代码实现
int profit(vector<int>& prices){
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]);
dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[n-1][0];
}
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if(n == 0)
return 0;
if(k>prices.size()/2) return profit(prices);
int dp[n][k+1][2];
for(int i=0;i<n;i++){
dp[i][0][0] = 0;
dp[i][0][1] = -INT_MAX;
for(int j=k;j>=1;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][k][0];
}
309-不限制交易次数,有一天冷却期
分析
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]);//买
//由于限制了一天的冷却期,即卖出股票后,你无法在第二天买入股票
//dp[i][1] = max(dp[i-1][1],dp[i-2][0]-prices[i]);//买
//初始化
dp[0][0] = 0; //第0天,手里没有股票,最大收益为0
dp[0][1] = -prices[0];//第0天,手里有股票,最大收益为-prices[0]
dp[1][0] = prices[0]>prices[1]?0:prices[1]-prices[0];//第1天,手里没有股票,判断如果第0天的价格大于第1天价格,那么最大收益为prices[1]-prices[0],否则为0
dp[1][1] = -(prices[0]>prices[1]?prices[1]:prices[0]);//第1天,手里有股票,判断如果第0天的价格大于第1天价格,那么最大收益为-prices[1]否则为-prices[0]
代码实现
int maxProfit(vector<int>& prices) {
if(prices.size() <= 1)
return 0;
int n = prices.size();
int dp[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = prices[0]>prices[1]?0:prices[1]-prices[0];
dp[1][1] = -(prices[0]>prices[1]?prices[1]:prices[0]);
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-不限制交易次数,有手续费
分析
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);//买
//由于有手续费,所以每次在进行买入操作时,再股票价格的基础上还要再掏手续费
//dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);//买
//初始化
dp[0][0] = 0; //第0天,手里没有股票,最大收益为0
dp[0][1] = -prices[0]-fee;//第0天,手里有股票,最大收益为-prices[0],再掏手续费
代码实现
int maxProfit(vector<int>& prices, int fee) {
if(prices.size() <= 1)
return 0;
int n = prices.size();
int dp[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0]-fee;
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]-fee);
}
return dp[n-1][0];
}
想说的话
感谢大佬提供的解题思路,一下子解决了这一类的问题,因为自己非科班出身,所以对于算法方面不是很了解,有这样优秀的题解真是帮了大忙, 当然上面的算法并不是最优的,还有一定的优化空间,后面也会不断的学习,大家加油~
最后,想问问各位大佬,这种方法算是动态规划还是穷举呢?