动态规划---股票问题
1.在推状态转移方程的途中,箭头的起始点表示前一天的状态,箭头的终点是当天的状态
2.当动态规划中涉及到多状态,且状态之间可以相互转换,要画图去分析
1.买卖股票的最佳时机含冷冻期
题目链接:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
题目解析:prices数组的数据表示当天股票的价格,要对一个股票不断进行交易,且当我们在前一天将股票卖出。那么第二天就会进入冷冻期,冷冻期间不能做任何的交易,计算并返回股票交易能够获得的最大利益。
算法讲解:动态规划
1.状态表示:经验+题目要求
在这道题中,可以将dp[i]表示为到第i天结束时,此时获得的最大利润,但是这道题中,涉及到 “买入”(买入就是会在当天手中有股票,既可以将股票卖出去,也可以不将股票卖出去),“可交易”(可交易就是手中没有股票,可以在当天买入股票,也可以不在当天买入股票),“冷冻期”(冷冻期就是当天不能做任何的交易)这三个状态,所以也可以将状态表示细分为3个状态
dp[i][0]表示:第i天结束后,处于“买入”状态,此时获得的最大利润
dp[i][1]表示:第i天结束后,处于“可交易”状态,此时获得的最大利润
dp[i][2]表示:第i天结束后,处于“冷冻期”状态,此时获得的最大利润
2.状态转移方程:
由于涉及到的状态有点多,可以画一张图来分析各个状态,如下图,分析状态之间能够相互转换,以当天的状态为买入为例,就要分别分析前一天的状态为买入,冷冻期和可交易时,能否转换为当天的买入状态
第一种情况:第i天是买入状态,即推算dp[i][0]
首先分析,能否由买入状态转换为买入状态,也就是当第i-1天的状态为买入状态时,能否转换为第i天的买入状态,此时是可以转换的,当前一天为买入状态时,只要前面一天不做任何的交易,那么第二天也是买入的状态。
其次分析, 能否由可交易状态转换为买入状态,也就是当第i-天是可交易状态时,能否转换为第i天的买入状态,此时是可以转换的,当前一天为可交易状态时,只要在前一天买入股票,第二天就可以处于买入状态了。
最后分析,能否有冷冻期状态转换为买入状态,此时是不可以转换的,如果前一天为冷冻期状态,前一天是不可以做任何交易的,所以在冷冻期的那一天是不可以买入股票的,由于前一天为冷冻期不能购入股票,所以第二天是不可能转换为买入状态的
如下图,根据下图来推算dp[i][0]时,也就是计算第i天结束后,处于“买入”状态,此时获得的最大利润,要计算dp[i][0],此时就要知道前一天的最大利润,因为只有买入状态和可交易状态能装换为买入状态,此时就要知道前一天处于买入状态获得的最大利润或者前一天处于可交易状态时获得的最大利润,根据状态表示,就可以推算出dp[i][0]的状态转移方程:
dp[i][0] = Math.max( dp[i-1][0] , dp[i-1][1] - prices[i] )
第二种情况:第i天是可交易状态,即推算dp[i][1]
首先分析,能否从买入状态转换为可交易状态,此时是不可以转换的,要想从买入状态转换为可交易状态,前一天就要将股票卖出去,但是根据题目要求如果在前一天将股票卖出去,第二天就会处于冷冻期状态了
其次分析,能否从可交易状态转换为可交易状态,当前一天是可交易状态时,只要在前一天不做任何的交易,第二天任然可以处于可交易的状态
最后分析,能否从冷冻期转换为可交易状态,前一天为冷冻期状态时,啥都不做,第二天就是可交易状态了,此时是可以转换成功的
此时去推算dp[i][1]时,也就是计算 第i天结束后,处于“可交易”状态,此时获得的最大利润,此时就要知道前一天为可交易状态获得的最大利润或者前一天为冷冻期状态获得的最大利润,此时dp[i][1]状态转移方程为:
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][2])
第三种情况:第i天是冷冻期状态,即推算dp[i][2]
首先分析,能否从买入状态转换为冷冻期状态,当前一天的状态是买入状态时,只要在前一天将股票卖出去,第二天就可以处于冷冻期状态了
其次分析,能否从可交易状态转换为冷冻期状态,当前一天是可交易状态时,由于手中是没有股票的,根本没有办法做出买股票的行为,所以此时就无法从前一天的可交易状态转换为第二天的冷冻期状态
最后分析,能否从冷冻期转换为冷冻期转态,此时不可能转换成功的,如果前一天的状态是冷冻期状态,冷冻期虽然啥交易都不能做,但是根据题目的要求,前一天是冷冻期,第二天就会是可交易状态了
此时去推算dp[i][2]时,也就是计算 第i天结束后,处于“冷冻期”状态,此时获得的最大利润,此时因为只能从买入状态转换为冷冻期状态,所以此时要知道前一天为买入状态时获得的最大利润,此时dp[i][2]的状态转移方程:
dp[i][2]=dp[i-1][0]+prices[i]
3.初始化
此时要初始化dp[0][0],dp[0][1],dp[0][2],dp[0][0]表示第一天之后就处于买入状态时获得的最大利润,所以此时dp[0][0]=-prices[0],dp[0][1]表示第一天之后处于可交易状态时获得的最大利润,第一天手中根本没有股票,所以将dp[0][1]=0,dp[0][2]表示第一天之后处于冷冻期状态此时获得的最大利润,第一天手中根本没有股票,所以将dp[0][2]初始化为0
4.填表顺序
从左往右,同时填3个表即可
5.返回值
返回Math.max(dp[n-1][0],Math.max(dp[n-1][1],dp[n-1][2]))即可
代码实现:
//我写的版本
class Solution {public int maxProfit(int[] prices) {int n=prices.length;int[] buy=new int[n+1];//买入int[] sell=new int[n+1];//可交易int[] ice=new int[n+1];//冷冻sell[0]=0;buy[0]=Integer.MIN_VALUE;ice[0]=0;for(int i=1;i<n+1;i++){buy[i]=Math.max(buy[i-1],sell[i-1]-prices[i-1]);sell[i]=Math.max(sell[i-1],ice[i-1]);ice[i]=buy[i-1]+prices[i-1];}return Math.max(buy[n],Math.max(sell[n],ice[n]));}
}//第一个版本
class Solution {public int maxProfit(int[] prices) {int n=prices.length;int[][] dp=new int[n][3];dp[0][0]=-prices[0];dp[0][1]=0;dp[0][2]=0;for(int i=1;i<n;i++){dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);dp[i][1]=Math.max(dp[i-1][1],dp[i-1][2]);dp[i][2]=dp[i-1][0]+prices[i];}return Math.max(dp[n-1][0],Math.max(dp[n-1][1],dp[n-1][2]));}
}//第二个版本
class Solution {public int maxProfit(int[] prices) {int n=prices.length;int[][] dp=new int[3][n];dp[0][0]=-prices[0];dp[1][0]=0;dp[2][0]=0;for(int i=1;i<n;i++){dp[0][i]=Math.max(dp[0][i-1],dp[1][i-1]-prices[i]);dp[1][i]=Math.max(dp[1][i-1],dp[2][i-1]);dp[2][i]=dp[0][i-1]+prices[i];}return Math.max(dp[0][n-1],Math.max(dp[1][n-1],dp[2][n-1]));}
}
2.买卖股票的最佳时机含手续费
题目链接:714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
题目解析:当完成一个股票的买入和卖出之后,才算一笔交易,当手中有股票时,必须先把股票卖出才能继续买入股票,计算并返回多次交易后能获得的最大利润,且每次交易都会付出手续费
算法讲解:动态规划
1.状态表示:经验+题目要求
题目要求我们返回获得的最大利润,所以dp[i]表示:第i天结束后,能获得的最大利润,但是在第i天的时候,是有买入状态和卖出状态这两种情况,所以,此时状态表示也要分为两种情况:
第一种情况:用buy[i]表示:第i天结束后,处于“买入”的状态,此时能够获得的最大利润
第二种情况:用sell[i]表示:第i天结束后,出入“卖出”的状态,此时能够获得的最大利润
2.状态转移方程
因为涉及到多状态且涉及到状态之间的转换,此时可以画图来分析不同状态之间能否相互转换,如下图
第一种情况:当天处于买入状态,推算buy[i]
首先分析,能否从第i-1天的买入状态转换为第i天买入转态,当第i-1天处于买入状态时,只要在第i-1天啥交易都不要做,第二天还是买入状态,此时是可以相互转换的
最后分析,能否从第i-1天的卖出状态转换为第i天的买入状态,当第i-1天为卖出状态时,因为第i-1天处于卖出状态,说明这一天已经把股票卖出去了,此时要想转换为买入状态,只要在第i-1天买入股票就行了,然后第i天就是买入状态了
如下图
此时推算buy[i]时,也就是计算第i天结束后,处于“买入”的状态,此时能够获得的最大利润,如果要知道第i天的最大利润,首先就要知道第i-1天时能够获得的最大利润,通过上面的分析知道,在计算buy[i]时,就要知道第i-1天之后处于买入状态时获得的最大利润或者第i-1天之后处于卖出状态时能够获得的最大利润,由于要求最大利润和根据状态表示,buy[i]的状态转移方程:
buy[i]=Math.max(buy[i-1],sell[i-1])
第二种情况:当处于卖出状态时,推算sell[i]
因为一次交易涉及到手续费,可以在买入股票或者卖出股票时去支付这个手续费,下面我是在卖出股票时支付手续费
首先分析,能否从第i-1天的买入状态转换为第i天的卖出状态时,当第i-1天处于买入状态时,只要在第i-1天将手中的股票卖出,第i天就可以处于卖出状态了(此时要支付手续费)
最后分析,能否从第i-1天的卖出状态转换为第i天的卖出状态,当第i-1天处于卖出状态,只要在第i-天啥交易都不做,就可以在第i天保持卖出的状态
根据上面的分析,推算sell[i]时,也就是计算第i天结束后,处于“卖出”的状态,此时能够获得的最大利润,如果要计算第i天能够获得的最大利润,首先就要知道第i-1天时能够获得的最大利润,因为第i-1天有两种情况,此时就要知道第i-1天之后处于“买入”时能获得的最大利润或者第i-1天之后处于“卖出”状态时能够获得的最大利润,此时计算利润时要记得支付手续费
sell[i]=Math.max(sell[i-1],buy[i-1]+prices[i]-fee)
总结如下图
3.初始化
此时由于初始化比较简单,就不用多创建空间来实现初始化了,根据细分的状态表示,只要将buy[0]=-prices[0],sell[0]=0即可
4.填表顺序
从左往右,同时填两个表即可
5.返回值
返回Math.max(buy[n-1],sell[i-1])即可
代码实现:
class Solution {public int maxProfit(int[] prices, int fee) {int n=prices.length;int[] buy=new int[n];int[] sell=new int[n];buy[0]=-prices[0];sell[0]=0;for(int i=1;i<n;i++){buy[i]=Math.max(buy[i-1],sell[i-1]-prices[i]);sell[i]=Math.max(sell[i-1],buy[i-1]+prices[i]-fee);}return Math.max(buy[n-1],sell[n-1]);}
}
3.买卖股票的最佳时机 IV
题目链接:188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
题目解析:最多可以做k次交易,每一次买入股票必须将手中的股票卖出去之后才能进行下一次的交易,计算并返回进行k次交易后能够获得的最大利润
讲解算法原理:
1.状态表示:经验+题目要求
根据题目要求,可以将dp[i]表示为第i天结束后,能够获得的最大利润,但是此时状态表示还可以细分,因为第i天结束1后,是会有两种情况的,分别是“有股票”和“没有股票” 这两种情况,此时就可以用f[i]来表示第i天结束后,处于“有股票”的状态,此时能够获得的最大利润,用g[i]表示第i天结束后,处于“没有股票的状态”,此时能够获得的最大利润。
但是因为存在交易次数的限制,所以在定义状态表示时,还要将交易次数表示出来,所以最终的状态表示为:
g[i][j]表示:第i天结束后,完成了j次交易,处于“有股票”的状态,此时获得的最大利润
f[i][j]表示:第i天结束后,完成了j次交易,处于“没有股票”的状态,此时获得的最大利润
2.推算状态转移方程
此时涉及到多状态之间转换的分析,依旧老样子,画图分析,如下图
第一种情况:第i天处于有股票状态,推算f[i][j]
首先分析,能否从有股票的状态转换为有股票的状态,当第i-1天处于有股票的状态时,要想在第i天还是处于有股票的状态,只要在第i-1天不做任何交易就行了,这样就能在第i天依旧保持有股票的状态,所以此时是可以转换的
最后分析,能否从没有股票的状态转换为有股票的状态,当第i-1天处于没有股票的状态时,要想在第i天处于有股票的状态,只要在第i-1天买入股票,第i天就可以处于有股票的状态,所以此时是可以转换的
如下图
则此时f[i][j]的状态转移方程:
f[i][j]=Math.max(f[i-1][j],g[i-1][j]-prices[i])
第二种情况:第i天处于没有股票状态,推算g[i][j]
首先分析,能否从没有股票的状态装换为没有股票的状态,当第i-1天处于没有股票的状态时,只要不在第i-1天作任何的交易,第i天就可以处于没有股票的状态,此时是可以转换的
其次分析:能否从有股票的状态转换为没有股票的状态,当第i-1天处于有股票的状态,要想在第i天处于有股票的状态,只要在第i-1天降股票卖出去即可,然后第i天就可以处于没有股票的状态了,但是此时要考虑交易次数,因为会将股票卖出去,此时完成一次交易,所以此时交易次数是要增加一次的
如下图
则此时g[i][j]的状态转移方程:
g[i][j]=Math.max(g[i-1][j],f[i-1][j-1]+prices[i])
推算g[i][j]的f[i-1][j-1]中为什么是j-1呢?
这里的i-1是指上一次交易,g[i][j]是指当前的交易,在有股票向没有股票的状态转换时,是完成了一次交易的,也就是推算这一次的g[i][j],为了推算第i次交易的最大利润,是要知道前一次交易的最大利润的,也就是f[i-1][j-1]
为了方便后面的初始化,可以转换一下g[i][j]的状态转移方程,将其分为两步:
第一步:g[i][j]=g[i-1][j]
第二步:if(j-1>=0) g[i][j]=Math.max(g[i][j],f[i-1][j-1]+prices[i])
3.初始化
由于在计算f[i][j]和g[i][j]时,由于会用到[i-1]行的值,所以要初始化第一行的值
初始化f[i][j]的第一行的值时,首先根据我们的状态f[i][j]的状态表示,要将f[0][0]=-prices[0],f[0][0]就表示第一天买入股票步进行任何交易获得的最大利润,由于是有交易次数的限制的,尽量不要在当天买入股票或者卖出股票之后去进行交易,因为同一天的股票价格是一样的,是不会获得利益的,由于在推算g[i][j]和f[i][j]时,用到的是max,因为不允许在同一天进行买入股票和卖出股票,所以尽量让同一天进行一次交易和进行两次交易的利润变得很小,让f[0][0]尽可能被选到,即让f[0][1]和f[0][2]都尽量初始化为很小,所以此时将dp[0][1]和dp[0][2]初始为一个最小值,这个最小值如何选择呢?此时不能选择Integer.MIN_VALUE,因为此时计算时有减的操作,Integer.MIN_VALUE减去一个数就会越界,所以此时选择0x3f3f3f3f,这个16进制数是max的一半
同理可得。初始化g[i][j]的第一行的值时,将g[0][0]=0,g[0][1]=-0x3f3f3f3f,g[0][2]=-0x3f3f3f3f,此时根据g[i][j]的第一种状态转移方程,本来还要初始化第一列的数据的,但是用了第二种的状态转移方程,只要对i-1的值进行一个合法判断就行了,此时,就不用初始化g表的第一列的数据了
4.填表顺序
大方向从上往下填每一行,填每一行时从左往右同时填两个表
5.返回值
返回g表中最后一行中的最大值即可,不用考虑f表,因为f表最终状态还是有股票的状态,没把股票卖出去,此时的利润肯定是没有最后将股票卖出去的利润大的
代码实现:
class Solution {public int maxProfit(int k, int[] prices) {int n=prices.length;int[][] f=new int[n][k+1];int[][] g=new int[n][k+1];for(int i=0;i<k+1;i++) f[0][i]=-0x3f3f3f3f;for(int i=0;i<k+1;i++) g[0][i]=-0x3f3f3f3f;f[0][0]=-prices[0];g[0][0]=0;for(int i=1;i<n;i++){for(int j=0;j<k+1;j++){f[i][j]=Math.max(f[i-1][j],g[i-1][j]-prices[i]);g[i][j]=g[i-1][j];if(j-1>=0) g[i][j]=Math.max(g[i][j],f[i-1][j-1]+prices[i]);}}int max=g[n-1][0];for(int i=0;i<k+1;i++){if(max<g[n-1][i]) max=g[n-1][i];}return max;}
}
4.买卖股票的最佳事假 III
该题的分析与第3题一模一样,不过就是将k变为2即可
代码实现:
class Solution {public int maxProfit(int[] prices) {int n=prices.length;int[][] f=new int[n][3];int[][] g=new int[n][3];for(int i=0;i<3;i++) f[0][i]=-0x3f3f3f3f;for(int i=0;i<3;i++) g[0][i]=-0x3f3f3f3f;f[0][0]=-prices[0];g[0][0]=0;for(int i=1;i<n;i++){for(int j=0;j<3;j++){f[i][j]=Math.max(f[i-1][j],g[i-1][j]-prices[i]);g[i][j]=g[i-1][j];if(j-1>=0) g[i][j]=Math.max(g[i][j],f[i-1][j-1]+prices[i]);}}int max=g[n-1][0];for(int i=0;i<3;i++){if(max<g[n-1][i]) max=g[n-1][i];}return max;}
}