股票买卖问题

记录几道股票买卖问题,学学动态规划。

力扣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;
      }
力扣121

力扣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;
    }
}
力扣122

力扣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];
    }
}
力扣714

力扣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]);
    }
}
力扣309

力扣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];//覆盖了买卖两次和买卖一次的答案
    }
}
力扣123

力扣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;
    }
}
力扣124
原文地址:https://www.cnblogs.com/shoulinniao/p/14204270.html