leetcode-股票系列的题目(动态规划)

一.买卖股票的最佳时机(一次交易)

题目

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

一次交易的最大收益=最低价格买进&最高价格卖出

所以第i天如果卖出的最大收益:第i-1天前完成交易的最大收益,或者是第i天卖出&i-1天以来最低价格买入

遍历每天的操作,记录如果是第i天卖出的收益,&第i-1天的最低价格(因为第i-1天以来的最低价格买入,第i天卖出,就是第i天卖出能够得到的最大的收入)

class Solution {
    public int maxProfit(int[] prices) {s        
        if(prices.length == 0){
            return 0;
        }
        //记录前i-1天的最低价格,选择那一天买入
        int min = prices[0];
        //记录最大收益
        int max = 0;
        for(int i=1; i<prices.length; i++){
            //最大收益是 今天卖出,之前 最低价格买进(所以更新完max再更新min)。还是原来的
            max = Math.max(max, prices[i]-min);
            min = Math.min(min, prices[i]);
        }
        return max;
    }
}

 

二.买卖股票的最佳时机II(无限次交易)

题目

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

贪心算法:无限次交易的话,就可以不用看何时达到最优,而是只要今天价格比昨天高,就可以昨天买入,今天卖出。这样每一次价格上涨的收益都可以赚到,就是股神的操作了...

贪心算法是在每一步总是做出在当前看来最好的选择,贪心在于“今天股价-昨天的股价”=正数/负数/0,那么只取正数,即只在有收益的时候购买就是当前看来最好的选择,就是该贪心算法的决策。

动态规划:(一般可以用贪心算法解决的问题,都可以考虑用动态规划)

1)定义状态dp[i][j]:第i天(具有前缀性质,考虑了之前天数的收益,算了max)是否持有股票(j=0代表不持有股票,j=1代表持有股票)能获得的最大收益;

2)状态转移方程:从不持有股票开始,最后的状态还是不持有股票。每一天的状态可以转移(买卖股票),也可以不操作;

dp[i][0]=Math.max(dp[i-1][0], dp[i-1][1]+prices[i]);//第i天不持有股票,可能是第i-1天就不持有,第i天不操作&第i-1天持有,第i天卖出

dp[i][1]=Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);//第i天持有股票,可能是第i-1天就持有,第i天不操作&第i-1天不持有,第i天买入

3)确定起始状态,初始化一些值,不然从什么值转移呢...:dp[0][0]=0,即第一天什么都不做,那么就是0;dp[0][1]=-prices[0],即买入股票,收益就是-prices[0];

4)确定终止状态:dp[len-1][0],即最后一天不持有股票的最大收益,因为dp[len-1][0]>dp[len-1][1],因为不持股,就会将股票转为现金

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length == 0){
            return 0;
        }
        //贪心算法
        int ret = 0;
        for(int i=1; i<prices.length; i++){
            if(prices[i]>prices[i-1]){
                ret += (prices[i]-prices[i-1]);
            }
        }
        return ret;
        //动态规划  
        /*int len = prices.length;
        //dp[i][0]表示第i天不持有股票,dp[i][1]表示第i天持有股票 的收益
        int[][] dp = new int[len][2];
        dp[0][0] = 0;
        //如果第0天买入,那么当前收益为-price[0]
        dp[0][1] = -prices[0];
        for(int i=1; i<len; i++){
            //第i天不持有股票,可能是i-1天就不持有,或者i-1天持有,但是第i天卖出
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]);
            //第i天持有,可能是i-1天就持有,或者i-1天买入
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
        }
        return dp[len-1][0];*/
    }
}

 

三.买卖股票的最佳时机含冷冻期

题目

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

    你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

考虑冷冻期:第i-1天卖出股票后,无法在第i天买入。因此是对买入时间有限制,所以会对第i天持有股票的收益有影响。

第i天持有股票的来源:第i-1天就持有/第i天买入,但是这里有一个前提是冷冻期,如果是第i天买入,必须是i-2天以前卖出了,即该状态的来源是i-2天以前不持有股票的最大收益。而不是之前没有冷冻期的i-1天不持有股票的最大收益。

如果i<2,即第一天或者第二天买入。那么只能是前两天都没有任何操作,因为一次买卖加上冷冻期是超过2天的,所以这种情况下之前的最大收益=0

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        if(len<1){
            return 0;
        }
        //dp[i][0]第i天不持有股票,dp[i][1]第i天持有
        int[][] dp = tiannew int[len][2];
        dp[0][1] = -prices[0];
        for(int i=1; i<len; i++){
            //第i天不持有:i-1天就不持有,今天不操作/i-1天持有,今天卖出
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]);

            //第i天持有:i-1天就持有/i-1不持有,今天买入,只能是i-2天以前卖的(如果i<2,那么只能是前两天都没有任何操作,因为一次买卖加上冷冻期是超过2天的,所以收益=0)
            dp[i][1] = Math.max(dp[i-1][1], (i>=2 ? dp[i-2][0] : 0)-prices[i]);
        }
        return dp[len-1][0];
    }
}

 

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

题目

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

如果没有手续费,其实问题又转化回了之前的,无限次交易求最大收益的场景,使用贪心算法即可。

如果有手续费,因为一次交易只需要缴纳一次手续费。所以可以选择在卖股票的时候,把手续费给减掉/在买股票的时候,把手续费给减掉。(下面的解法是卖股票的时候,在收益里把手续费减掉)

class Solution {
    public int maxProfit(int[] prices, int fee) {
        if(prices.length<1){
            return 0;
        }
        //如果没有手续费,那么就化为k次交易,之前的贪心算法
        if(fee==0){
            int ret = 0;
            for(int i=1; i<prices.length; i++){
                if(prices[i]>prices[i-1]){
                    ret += (prices[i]-prices[i-1]);
                }
            }
        }
        //dp[i][0]表示第i天不持有股票 的最大收益,dp[i][1]表示第i天持有股票
        int[][] dp = new int[prices.length][2];
        //初始化
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i=1; i<prices.length; i++){
            //第i天不持有股票的最大收益:前一天就不持有,不操作/今天卖出
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]-fee);
            //第i天持有股票的最大收益:前一天就持有,不操作/今天买入
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-prices[i]);
        }
        return dp[prices.length-1][0];
    }
}

 

五.买卖股票的最佳时机III(最多两次交易)

题目

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

最多两次交易==一次/两次/不交易

类似于只进行一次交易的思路。那么就是再多加一次交易,第二次交易的买入值应该是在减去第一次收益的基础上的,这样第二次交易结束的收益就是两次交易的总收益了。

    public int maxProfit(int[] prices) {
        int buy1 = Integer.MAX_VALUE;
        int buy2 = Integer.MAX_VALUE;
        int sell1 = 0;
        int sell2 = 0;
        for(int price : prices) {
       //第一次买入的价格,应该是之前的最低价 buy1
= Math.min(buy1, price);
       //第一次的收益,这个今天不持有股票的状态可以来自于可能来自于今天卖出之前的/不操作 sell1
= Math.max(sell1, price - buy1);
       //第二次买入的价格,因为要结合两次交易的收益,那么就需要减去第一次的收益 buy2
= Math.min(buy2, price - sell1);
       //两次买卖的收益,可能来自于今天卖出之前的/不操作 sell2
= Math.max(sell2, price - buy2); } return sell2; }

 

六.买卖股票的最佳时机IV(最多k笔交易)

题目

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

题解

这个题是上一个题的泛化版,当k=2时,就是上一个题了。

当k>=prices.length/2时,因为买卖算一天,那么就是每天都可以交易,那其实相当于可以交易无限次,使用贪心算法。

否则就是还是使用动态规划。

遍历每一天,把每一天模拟k次交易,不断更新最大收益值。第二次买的时候,价格考虑了用第一次赚的钱去补贴一部分。那么交易次数必须作为一个新的维度被考虑进来,这个时候仅仅二维就不够了

1)定义状态dp[i][k][j]:第i天(具有前缀性质,考虑了之前天数的收益,算了max),第k次买卖股票,是否持有股票(j=0代表不持有股票,j=1代表持有股票)能获得的最大收益;

2)状态转移方程:从不持有股票开始,最后的状态还是不持有股票。每一天的状态可以转移(买卖股票),也可以不操作;

dp[i][j][0]=Math.max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);//第i天不持有股票,可能是第i-1天就不持有,第i天不操作&第i-1天持有,第i天卖出,所以两天买入股票的次数都是j

dp[i][j][1]=Math.max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);//第i天持有股票,可能是第i-1天就持有,第i天不操作&第i-1天不持有,第i天买入,此时昨天买入的次数是j-1

3)确定起始状态:dp[i][0][0]=0,即什么都不做,没有买入过股票,手头也没有持股,那么就是0;dp[i][0][1]=Integer.MIN_VALUE,即没有买入过股票,那么不可能持股,使用整型的最小值表示这种不可能性;dp[0][1..k][0]=0,dp[0][1...k][1]=-prices[0],即第一天至多可以持有一笔交易,如果持有那么当前收益就是-prices[0],如果不持有当前收益是0

4)确定终止状态:dp[len-1][k][0],即最后一天不持有股票的最大收益

class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length<1){
            return 0;
        }
        if(k >= prices.length/2){
            return greedy(prices);
        }
        int[][][] dp = new int[prices.length][k+1][2];
        //初始化
        for(int i=0; i<prices.length; i++){
            dp[i][0][0] = 0;
            dp[i][0][1] = Integer.MIN_VALUE;
        }
        for(int i=1; i<=k; i++){
            dp[0][i][0] = 0;
            dp[0][i][1] = -prices[0];
        }
        //状态转移
        for(int i=1; i<prices.length; i++){
            for(int j=k; j>=1; j--){
                dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);
                dp[i][j][1] = Math.max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);
            }
        }
        return dp[prices.length-1][k][0];
    }

    public static int greedy(int[] prices){
        int sum = 0;
        for(int i=1; i<prices.length; i++){
            if(prices[i]>prices[i-1]){
                sum += (prices[i]-prices[i-1]);
            }
        }
        return sum;
    }
}

对三维数组进行压缩,去掉最高维度的天数,用dp[k][2]来代替.

因为初始状态为dp[0][0],所以第一次买入dp[0][1],第一次卖出dp[1][0];第二次买入dp[1][1],第二次卖出dp[2][0];第k次买入dp[k-1][1],第k次卖出dp[k][0]

状态转移公式为:dp[j-1][1]=max(dp[j-1][1], dp[j-1][0]-prices[i]),即第j次买入状态来源于本次不操作/第j-1次不持有股票,本次买入

 dp[j][0]=max(dp[j][0], dp[j-1][1]+prices[i]),即第j次卖出状态来源于本次不操作/第j-1次持有股票,本次卖出

为什么逆序?因为对dp进行压缩后,少了一个维度,正序遍历会出现状态覆盖的情况。

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length<1){
            return 0;
        }
        int k = 2;
        //dp[i][0]表示第i次不持有股票,dp[i][1]表示第i次持有股票。去掉天数这个维度
        int[][] dp = new int[k+1][2];
        //初始化
        for(int i=0; i<=k; i++) {
            dp[i][0] = 0;
            dp[i][1] = -prices[0];   } //动规 for(int i=1; i<prices.length; i++) { for(int j=k; j>0; j--) { //第j次持有股票的最大收益:不做操作/买入(价格是基于前一天的收益 的差价) dp[j-1][1] = Math.max(dp[j-1][1], dp[j-1][0]-prices[i]); //第j次不持有的最大收益:不做操作/卖出 dp[j][0] = Math.max(dp[j][0], dp[j-1][1]+prices[i]); } } return dp[k][0]; } }

 

posted @ 2020-09-08 21:36  闲不住的小李  阅读(546)  评论(0编辑  收藏  举报