为什么有人夜里碰到股票问题,辗转反侧睡不着觉?为什么有人看到股票问题心理欢喜直接操作?你是想做哪类人?今天就揭秘股票问题,让你应对股票问题的时候可以如鱼得水。
这种问题一看就是动态规划问题,动态规划说难听一点就是穷举,但是我们应该如何的去聪明的穷举,这是我们需要考虑的
给定一个数组,它的第
i
个元素是一支给定的股票在第i
天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
股票l只是进行了一次交易,相当于k=1;股票II相当于不限制交易次数,k=inf;股票III只是进行了2次交易,相当于k=2;剩下的题目也是不限次数,但是增加了一些额外的条件,其实解题步骤也是大差不差的,好了,我们言归正传,开始我们的解密之旅
动态规划的核心就是状态和转移(选择),首先,我们看这道题,它一天中可能有几种状态,它的每种状态又有几种可能,我们要根据状态去找出相对应的选择,我们是不是要穷举所有的状态,然后在根据选择去更新状态,看下列穷举的模板:
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 择优(选择1,选择2...)
每天都有3种选择:买入、卖出、无操作,我们简单的用buy、sell、none表示这三种选择
但是这买卖还有其他条件的限制,sell必须在buy的后面,buy的前提是k>0(还可以操作的次数),none也可以分为两种状态:一种是持有股票进行,一种是没有持有股票进行,这几种情况才是比较难想的,但是想到了以后,这种题就迎难而解了
那么我们现在这个问题的状态总共有3个,一个是天数、一个是可以操作的次数、一个是是否持有股票,我们用代码来展示一下:
dp[i][k][0 or 1];
0<=i<=n-1 1<=k<=K;
n为天数,大K为交易的上限,0和1代表的是是否持有股票
然后我们根据穷举的模板就能写出得出:
for(int i=0;i<n;i++){
for (int k = 1; k <=K; k++) {
for(int s=0;s<2;s++){
dp[i][k][s]=max(buy,sell,none);
}
}
}
我们现在想要求的是dp[n-1][K][0]的值,含义就是在最后一天,最多允许K次交易,最多可以获得多少的利润
现在的穷举的框架已经给了,接下来状态进行进行转移呢?
通过这个图我们可以很清楚的看到,每种状态是如何转移而来的,我们试着写一写状态转移方程
dp[i][k][0]=max(dp[i-1][k][0]),dp[i-1][k][1]+prices[i])
max( 选择了无操作 选择了卖出 )
dp[i][k][0]:我今天没有持有股票,最大的交易次数为k,可以推出昨天的情况:
- dp[i-1][k][0]:昨天就没有持有股票,截止昨天为止最大的交易次数还是k
- dp[i-1][k][1]+prices[i]:昨天持有股票,截止昨天最大交易数限制为k,今天进行了sell
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 今天选择 none, 今天选择 buy )
dp[i][k][1]:我今天持有股票,最大的交易次数为k,那么我们就可以推出昨天的情况:
- dp[i-1][k][1]:我昨天就持有股票,截止昨天的最大交易次数为k,今天进行了none
- dp[i-1][k-1][0]-prices[i]:我昨天没有持有,截止昨天的最大交易次数为k-1,我今天选择了buy
现在我们已经完成了最艰难的一步了,如果之前的内容你都可以理解,恭喜你,你已经可以解决这类型问题了,看究极模板:
dp[-1][...][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。
dp[-1][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。
dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
将上图的状态转移方以及base进行总结得到:
base case:
dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][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])
当数组中的k不会发生改变时,我们可以省略掉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])
这道题最大的操作数是2,所以k对结果的影响是不能被忽略掉的,所以我们单套模板的话,会直接超时,所以我们要对代码进行改进,穷举k的每种状态,再进行选择
//买卖股票
int[][] dp;
public int maxProfit(int[] prices) {
//对入参进行判断
if (prices == null || prices.length == 0) {
return 0;
}
//最大操作数是2
int maxK=2;
int n=prices.length;
int[][][] dp=new int[n][maxK+1][2];
for(int i=0;i<n;i++){
//穷举k的每一种状态
for(int k=maxK;k>=1;k--){
//因为下面是[i-1],i是从0开始的,所以i-1=-1的情况是存在的
if(i-1==-1){
//处理i=0的时候的base情况
dp[i][k][0]=0;
dp[i][k][1]=-prices[i];
continue;
}
//持有股票
dp[i][k][1]=Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
//无股票
dp[i][k][0]=Math.max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
}
}
return dp[n-1][maxK][0];
}
这道题就讲解完了,你对股票问题是否有了不一样的收获,这道题的关键还是在聪明的穷举出所有的可能的状态,基于base的基础上慢慢的向后推进,相信你有举一反三的能力,其他题也和这道题的解题方法几乎差不多,