记录几道股票买卖问题,学学动态规划。
力扣121:买卖股票的最佳时机
题解:只能买卖一次获取最大利润,求数组最大上升差。用一个变量minn存储最低点,另一个变量maxx维护最大利润。
public int maxProfit(int a[]) { int minn=Integer.MAX_VALUE, maxx=0, n=a.length; for(int i=0;i<n;i++) { if(minn>a[i]) minn=a[i]; else maxx=Math.max(maxx, a[i]-minn); } return maxx; }
力扣122:买卖股票的最佳时机II
题解:相比上一题可以买卖无数次,即累计所有上升序列序列之差。
class Solution { public int maxProfit(int[] a) { int len=a.length; int sum=0; for(int i=1;i<len;i++){ if( a[i-1]<a[i] ) sum+=a[i]-a[i-1]; } return sum; } }
力扣714:买卖股票的最佳时机含手续费
题解:含手续费则需要计算卖出是否值得,并且在最合适的时候卖出,可以交易无限次。用纯利润计算,买入(持有)则花钱(-a[i]),卖出(不持有)则赚钱(+a[i]-fee)。买与不买,卖与不卖,很容易看出是动态规划。
dp[i][0]表示第i天不持有股票,昨天和今天的持有情况可能是:
- 昨天不持有,今天也不持有。dp[i-1][0];
- 昨天持有,今天不持有。dp[i-1][1]+a[i];
- dp[i][0] = max(dp[i-1][0], dp[i-1][1]+a[i]-fee);
dp[i][1]表示第i天持有股票,昨天和今天的持有情况可能是:
- 昨天持有,今天持有。dp[i-1][1];
- 昨天不持有,今天持有。dp[i-1][0]-a[i];
- dp[i][1] = max(dp[i-1][1], dp[i-1][0]-a[i]);
全部计算都是建立在昨天的状态上,可以滚掉一维。
class Solution { public int maxProfit(int[] a, int fee) { int n=a.length; int[] dp=new int[2];//dp[0]表示不持有股票 dp[1]表示持有股票 dp[0]=0; dp[1]=-a[0]; for(int i=1;i<n;i++) { int temp=dp[0]; dp[0]=Math.max(dp[0], dp[1]+a[i]-fee);//昨天不持有 或者 昨天持有,今天卖了 dp[1]=Math.max(dp[1], temp-a[i]);//昨天持有 或者 昨天不持有,今天买了 } return dp[0]; } }
力扣309:最佳买卖股票时机含冷冻期
题解:卖出股票后有一天冷冻期,相比上一题,衍生出另一种状态不持有(冷冻期),重新分析。
dp[0]表示不持有(非冷冻期),昨天和今天的情况可能是:
- 昨天不持有(非冷冻期)dp[0];
- 昨天不持有(冷冻期)dp[2];
- dp[0] = max(dp[0], dp[2]);
dp[1]表示持有,昨天和今天的情况可能是:
- 昨天持有,今天继续持有;
- 昨天不持有(非冷冻期),今天持有;
- dp[1] = max(dp[1],dp[2]-a[i]);
dp[2]表示不持有,必然是昨天持有,今天卖出:dp[2]=dp[1]+a[i];
class Solution { public int maxProfit(int[] a) { int n=a.length,temp0,temp1,temp2; if(n==0) return 0; int[] dp=new int[3];//0表示不持有 1表示持有 2表示冷冻期(不持有) dp[0]=0; dp[1]=-a[0]; dp[2]=0; for(int i=1;i<n;i++) { //不持有非冷冻期的情况:昨天不持有(非冷冻期) 昨天不持有(冷冻期) temp0=Math.max(dp[0], dp[2]); //昨天持有,今天持有 vs 昨天不持有(非冷冻期),今天持有 temp1=Math.max(dp[1], dp[0]-a[i]); //冷冻期必然是昨天持有,今天刚卖出 temp2=dp[1]+a[i]; dp[0]=temp0; dp[1]=temp1; dp[2]=temp2; } return Math.max(dp[0], dp[2]); } }
力扣123:买卖股票的最佳时机 III
题解:只能买卖2次,重新分析状态
- dp[0]表示啥也不干,不买不卖,永远为0;
- dp[1]表示第一次买入的状态,可以从原本就买入(dp[1])或者 当天买入(dp[0]-a[i])获得该状态;
- dp[2]表示第一次卖出的状态,可以从原本就卖出(dp[2])或者 当天卖出(dp[1]+a[i])获得该状态;
- dp[3]表示第二次买入的状态,可以从原本就第二次买入(dp[3])或者 当天第二次买入(dp[2]-a[i])获得该状态;
- dp[4]表示第二次卖出的状态,可以从原本就第二次卖出(dp[4])或者 当天第二次卖出(dp[3]+a[i])或者该状态;
class Solution { public int maxProfit(int[] a) { int n=a.length; if(n==0) return 0; int[] dp=new int[5]; //什么都不干(永远为0)、第一次买入、第一次卖出、第二次买入、第二次卖出 dp[1]=dp[3]=-a[0];//dp[3]初始化表示舍弃掉一次机会,只买卖一次 dp[2]=dp[4]=0; for(int i=1;i<n;i++) { dp[1]=Math.max(dp[1], dp[0]-a[i]); dp[2]=Math.max(dp[2], dp[1]+a[i]); dp[3]=Math.max(dp[3], dp[2]-a[i]); dp[4]=Math.max(dp[4], dp[3]+a[i]); } //return Math.max(dp[2], dp[4]); return dp[4];//覆盖了买卖两次和买卖一次的答案 } }
力扣124:买卖股票的最佳时机 IV
题解:可以买卖k次股票,题目k巨大,买卖次数其实不超过天数的一半。
这里有个概念要定义清楚,买入算不算交易一次,卖出算不算交易一次,买入后卖出算几次?
dp[天数][持有状态,0表示不持有,1表示持有][卖出次数];
- 今日不持有状态,可以从两种状态衍生出来:【昨天持有,今天卖出】或【昨天本来就不持有】;
- dp[i][0][j] = Math.max( dp[i-1][1][j-1]+a[i], dp[i-1][0][j] );
- 今日持有状态,可以从两种状态衍生出来:【昨天没有,今天买入】或【昨天本来就有】,注意买入是不加j的;
- dp[i][1][j] = Math.max( dp[i-1][0][j]-a[i], dp[i-1][1][j] );
取值当然是取不持有(卖出),maxx=Math.max(maxx, dp[i][0][j]);
对于j=0,如果遍历到的当天还没有卖出过,那么选择最低买入价,dp[i][1][0]=Math.max(dp[i-1][1][0], dp[i-1][0][0]-a[i]);。
class Solution { public int maxProfit(int k, int[] a) { int n=a.length; if(n<2 || k==0) return 0; k=Math.min(n/2, k); //dp[天数][是否持有股票][卖出次数] int[][][] dp=new int[n][2][k+1]; //初始化每个卖出次数,在持有(买入)状态下为绝对小 for(int i=1;i<=k;i++) { dp[0][1][i]=-10000; } //然后从第一天开始去遍历,后续会比较 绝对小和真实买卖状态,从而覆盖掉绝对小 //这个绝对小不可以是0,否则会覆盖掉真实的卖出状态例如-2之类的 dp[0][1][0]=-a[0]; int maxx=0; for(int i=1;i<n;i++) { //如果当天还没有卖出过,那么选择最低买入价 dp[i][1][0]=Math.max(dp[i-1][1][0], dp[i-1][0][0]-a[i]); for(int j=1;j<=k;j++) { //昨天持有,今天卖出,刚好j次 vs 原本就卖出j次的状态值 dp[i][0][j] = Math.max( dp[i-1][1][j-1]+a[i], dp[i-1][0][j]); //前一天不持有,当天买入 vs 原本就卖出j次的买入状态 dp[i][1][j] = Math.max( dp[i-1][0][j]-a[i], dp[i-1][1][j] ); maxx=Math.max(maxx, dp[i][0][j]); } } return maxx; } }