买卖股票问题系列总结

1.买卖股票的最佳时机

暴力搜索

因为只是要一天的,意思就是只要挑一天最小的买入,最大的卖出就可以了,转换过来就是求数组中的两个数最大的差值(前提是下标大的减下标小的,下面遍历就会看出来),那么只需要双指针,一个指向买入那天,然后另一个往后面找,后面那天的股票钱-买入那天的股票的钱。然后暴力找到最大的那个。

时间复杂度是n^2,空间复杂度是1,但是超时了

class Solution {
    public int maxProfit(int[] prices) {
        int result=0;
        for(int i=0;i<prices.length;i++){
            for(int j=i+1;j<prices.length;j++){
                result=Math.max(result,prices[j]-prices[i]);
            }
        }
        return result;
    }
}

贪心解决

贪心的思维就是选左边最小的那个,然后右边最大的那个减去它就好了。所以low一直在找,last也是一边走一边找。时间复杂度是n,空间复杂度是1

class Solution {
    public int maxProfit(int[] prices) {
        int result=0;
        int low=Integer.MAX_VALUE;
        for(int i=0;i<prices.length;i++){
                low=Math.min(low,prices[i]);
                result=Math.max(result,prices[i]-low);
        }
        return result;
    }
}

动态规划思路

这道题求的是最大的卖出股票的金额数。问题是怎么使用dp?dp[i] [0],dp[i] [0]这里i是第几天,0是不持有股票,1是持有股票。那么问题来了如何递推?为什么要这样子定义?为什么一定是两种?原因就是每一天都可以选择持有或者是不持有股票,或者是那天出售股票。dp的第二维是0的时候不持有股票可能是两种情况,第一种就是没有股票,第二种就是卖出了获得了利润。如果是1的时候也是有两种情况,这里主要就是求最小的买股票钱,也可以说是持有股票,那么买入是不是就是负数,如果是负数那么在这里的最小股票钱应该就是最大。比如1和2,买了之后是不是就是-1,-2。最后的递推就是

  • dp[i] [0]=Math.max(dp[i-1] [0] ,dp[i-1] [1]+prices[i]);两种情况,卖出去得到利润,或者说是什么都不干,保持昨天的情况(可能昨天卖出去的利润已经是最高了)

  • dp[i] [1]=Math.max(dp[i-1] [1],-prices[i]);这种就是找最小的那个股票数。

  • 可以这么看,第一个再求最大的利润,第二个再求买入最小的钱。那么关系就是要通过对比昨天的最优选择得到的最高利润与今天的最高利润对比。取出最大。同时买入股票的最小钱也在变化。

class Solution {
    public int maxProfit(int[] prices) {
        int len=prices.length;
        if(len<2) return 0;
       int[][] dp=new int[len][2];
       dp[0][0]=0;
       dp[0][1]=-prices[0];
       for(int i=1;i<len;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],-prices[i]);
       }
       
       return dp[len-1][0];
    }
}

2.买卖股票的最佳时机 II

暴力搜索思路

但是这个暴力很有技巧,因为不能够一直买或者是一直卖,而且一开始一定是需要先买入。股票只能够一买一卖,也就是需要一个状态记录当前到底持有股票还是没有持有股票。不操作是一定要走的分支,但是买卖就看当前的状态是什么,只能做一个,并且继续往下执行。相当于还是二叉树的回溯搜索。

class Solution {
    int res=0;
    public int maxProfit(int[] prices) {
        if(prices.length<2) return 0;
        //一开始没有持有股票
        dfs(prices,0,0,0);
        return res;
    }

    public void dfs(int[] prices,int index,int status,int profit){
        if(index==prices.length){
             res=Math.max(res,profit);
             return;
        }

        //不进行操作
        dfs(prices,index+1,status,profit);

        if(status==0){
            //因为没有持有那么就可以选择买j今天股票
            dfs(prices,index+1,1,profit-prices[index]);
        }else{
            //持有之前股票,可以选择这天卖股票
            dfs(prices,index+1,0,profit+prices[index]);
        }
    }
 }

动态规划思路

这里其实就是变成了要处理多次交易,也就是说,每次持有股份的时候不再是只持有一份(包括以前持有被卖掉的),每次计算股份的时候仍然需要最大值,也就是只有你持有股份最大值的时候才是买入最少前的时候。但是这次转移方程不同了,只不过就是去找前一天中没有持有股份最大利润+买入今天的股份(负数),或者是不买入,持有昨天的股份这里包括了利润(可能是前天)。也就是比起前一道题dp[i] [1]需要考虑多利润的问题,但是本质还是一样,昨天持有股份的时候保留的钱最多+今天卖出的股份的钱那么就是今天最大的利润。

转移到现实的计算问题会更好理解,可以自己计算一下。

class Solution {
    public int maxProfit(int[] prices) {
        int len=prices.length;
        if(len<2) return 0;

        int[][] dp=new int[len][2];
        dp[0][1]=-prices[0];
        for(int i=1;i<len;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][0]-prices[i]);//昨天最大利润(没有持有股份)-买入的钱(持有股份)
        }
        return dp[len-1][0];
    }
}

贪心解法

思路其实很简单就是每次都是加正数。股票买卖变成了利润只要后一天减去前一天的利润是大于0的那么就可以执行交易。这种思路其实局部最优,比如现在是1 ,2 ,3 , 2,3,4那么是不是2-1和3-2都是正数,一旦后面有一个小于3的那么就无法执行,因为不做亏本生意。-1+2-2+3实际上又可以看成是3-1所以只需要获取每一段连续获取利润的总值并且加上每一段局部最优就能够得到总体最优。也就是(-1+2-2+3)+(-3+4)就能得到最优

class Solution {
    public int maxProfit(int[] prices) {
        int len=prices.length;
        if(len<2) return 0;
        int res=0;
        for(int i=1;i<len;i++){
            if(prices[i]-prices[i-1]>0){
                res+=(prices[i]-prices[i-1]);
            }
        }
        return res;
    }
}

3.买卖股票的最佳时间3

动态规划

题目的意思其实就是买两次最大利润。也就是限制了买的数量。但是只要延续之前的做题思路基本上这道题是相对好理解的。dp[i] [j] [k]这里i是第几天,j是是否持有股票,k是卖出几次。然后根据之前的思路,只能够卖出一次的时候,那么都是根据前一天的来进行处理。这里状态很多种一个一个解释。实际上你会发现只要单独抽出来,就是选择卖出最好的一次交易,然后再次选择卖出最好的第二次交易。每次都需要根据前面几次的买卖处理。

理清楚情况到底有多少种(还要注意,如果Ingeger. MIN_VALUE-1就是MAX_VALUE所以这就是除以2的理由)

  • 买入第一次股票(依赖昨天什么都没做但是今天买入股票的最大利润或者是昨天的持有股票最大利润)

  • 买入第一次股票,并且出售第一次(依赖昨天持有股票最低利润,且没有进行交易,或者是已经交易一次的最大利润)

  • 买入第一次股票,出售第一次,第二次买入(依赖昨天没有持有股票但是已经交易一次的最大利润,或者是昨天交易一次之后持有更低的股票)

  • 买入第一次股票,出售第一次,第二次买入,第二次进行出售。(依赖昨天持有股票且进行一次交易的最大利润和昨天的已经买入两次股票持有的最大利润)

相当于每次选择都是依赖昨天的前一种选择。

class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null||prices.length<=1) return 0;
        int len=prices.length;
        int[][][] dp=new int[len][2][3];
        
        dp[0][0][0]=0;
        dp[0][1][0]=-prices[0];
        dp[0][0][1]=Integer.MIN_VALUE/2;
        dp[0][1][1]=Integer.MIN_VALUE/2;
        dp[0][0][2]=Integer.MIN_VALUE;
        dp[0][1][2]=Integer.MIN_VALUE/2;
        
        for(int i=1;i<len;i++){
            //什么都不做,没卖出也不持股。
            dp[i][0][0]=0;
            //持股,要么就是今天买,要么就是按照昨天的持股方案
            dp[i][1][0]=Math.max(dp[i-1][0][0]-prices[i],dp[i-1][1][0]);
            //不持有股,卖出一次,那么可以选择状态是今天卖出,或者是沿用昨天卖出的利润
            dp[i][0][1]=Math.max(dp[i-1][1][0]+prices[i],dp[i-1][0][1]);
            //持股,并且已经卖出一次,要么就是今天买股(要在卖出一次的时候),要么就是沿用昨天的买股方案
            dp[i][1][1]=Math.max(dp[i-1][0][1]-prices[i],dp[i-1][1][1]);
            //不持有股份,并且卖出第二次,卖出今天或者是昨天已经卖出两次的利润选择。
            dp[i][0][2]=Math.max(dp[i-1][1][1]+prices[i],dp[i-1][0][2]);
            
           
            dp[i][1][2]=Integer.MIN_VALUE;
        }
        return Math.max(0,Math.max(dp[len-1][0][1],dp[len-1][0][2]));
    }
}

其实思路很相似,模拟一下发现几乎差不多。这里不同的地方就是sell1和buy2还有sell2都是使用了考虑今天的交易的利润。也就是不像上面完全是昨天的。但是就算考虑了今天也不会影响答案,因为,昨天高的还是会延续下来,今天高的可以直接考虑今天的。而且最后sell2这个地方假设卖一次利润最高,sell1可以通过同一天买入卖出来(转移方程本来就有这个特点。)转移到sell2。

class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null||prices.length<=1) return 0;
        int buy1=-prices[0];
        int sell1=0;
        int buy2=-prices[0];
        int sell2=0;
        
        for(int i=1;i<prices.length;i++){
            buy1=Math.max(buy1,-prices[i]);
            sell1=Math.max(sell1,buy1+prices[i]);
            buy2=Math.max(buy2,sell1-prices[i]);
            sell2=Math.max(sell2,buy2+prices[i]);
        }

        return sell2;
        
    }
}

4.买卖股票的最佳时间3

动态规划

首先就是明确使用的dp。这里使用的是buy和sell。

  • buy[i] [j]表示的是持股状态下,第i天,卖出j的最大利润
  • sell[i] [j]表示的是不持股状态下,第j天,卖出的最大利润。

也就是只需要两层for,一层是遍历股票,一层是遍历卖出去的次数。整体思路完全和卖两次一样。但是对于初始化要求很高。

  • buy[0] [0]的时候可以持股prices[0],sell[0] [0]的时候可以不持股也没卖出所以是0
  • 如果是buy[0] [1…k]和sell[0] [1…k]因为都没有卖出过,所以是不合理的,没有任何利润。所以只需要赋值最小的值就好了
  • 还有一个就是在遍历的股票的时候buy[i] [0]是需要初始化的,要么就是延续昨天的持有股票要么就是在今天持有股票。相当于就是只有一个物品的时候的初始化。因为buy[0] [0]的初始化是一定会有股票买入,所以相当于就是在对比哪天第一次买入的股票便宜的。而不是一直都是0
  • 还有就是交易最多是n/2次只需要让k取k和n/2之间的最小值。向下取整。最多只能交易这么多次
class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length==0) return 0;
        
        int len=prices.length;
        //不可能交易超过k
        k=Math.min(k,len/2);
        int[][] buy=new int[len][k+1];
        int[][] sell=new int[len][k+1];
        
        buy[0][0]=-prices[0];
        sell[0][0]=0;
        for(int i=1;i<=k;i++){
            buy[0][i]=sell[0][i]=Integer.MIN_VALUE/2;
        }
        
        for(int i=1;i<len;i++){
            buy[i][0]=Math.max(buy[i-1][0],sell[i-1][0]-prices[i]);
            for(int j=1;j<=k;j++){
                buy[i][j]=Math.max(buy[i-1][j],sell[i-1][j]-prices[i]);
                sell[i][j]=Math.max(sell[i-1][j],buy[i-1][j-1]+prices[i]);
            }
        }

        return Arrays.stream(sell[len-1]).max().getAsInt();
        
        
    }
}

5.买卖股票时机含冷冻期

动态规划

不要管冷冻期,不要去想后一天怎么处理。只需要前两天是不是冷冻期就可以了。

dp的定义

  • dp[i] [0]就是第i天不持有股份,而且不在今天卖出,可能是之前卖出直到现在没有进行处理。
  • dp[i] [1]第i天持有股份,就在今天买入股份
  • dp[i] [2]第i天不持有股份,而且必须得在今天卖出

递推关系其实就是

  • dp[i] [0]那么考虑的范围就是dp[i-1] [0]前一天不持有股份而且没有做任何事的最大利润,第二种就是前天刚刚不持有股份,而且在前天卖出。dp[i] [2]
  • dp[i] [1]就是昨天的持有股份dp[i-1] [0],或者是昨天没有持有股份而且没有做任何事的dp[i-1] [1]才能够买入股票。dp[i] [2]刚刚卖出。说明今天是冷冻期不能够做任何事情,所以不考虑dp[i] [2]的转换。
  • dp[i] [2]不持有股票而且要今天卖出那么前天一定就是要持有股票dp[i-1] [1]+prices[i]
class Solution {
    public int maxProfit(int[] prices) {
         int len=prices.length;
         
         int[][] dp=new int[len][3];
         dp[0][0]=0;
         dp[0][1]=-prices[0];
         dp[0][2]=0;

         for(int i=1;i<len;i++){
             dp[i][0]=Math.max(dp[i-1][0],dp[i-1][2]);
             dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
             dp[i][2]=dp[i-1][1]+prices[i];
         }
         
         return Math.max(dp[len-1][0],dp[len-1][2]);
    }
}

6.买卖股票的最佳时机含手续费

动态规划思路

其实就是多了个费用而已。多次买卖得到最大利润没什么区别。参考上面即可

class Solution {
    public int maxProfit(int[] prices, int fee) {
           int len=prices.length;

           int[][] dp=new int[len][2];
           
           dp[0][0]=0;
           dp[0][1]=-prices[0];
           
           for(int i=1;i<len;i++){
               dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]-fee);
               dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
           }

           return dp[len-1][0];
           
           
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值