LeetCode -- 买卖股票系列问题 动态规划思想

121. 买卖股票的最佳时机

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

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

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

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

解题思路

只买卖一次就比较简单了 ,只需用两个变量记录最低价格和最大收益。

如果当前处理的价格高于最低价格,则尝试更新最大收益。否则,更新最低价格。

初始化最大收益res=0,初始化最低价格就是第一天的价格。

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

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

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

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

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

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

解题思路

动态规划 

首先定义状态 对于每一天都是两种状态 持有股票0和不持有股票1

       (应该明确状态为 "当天是否持有股票" 而不是 "当天买或者卖")

        所以dp[i][0] dp[i][1]就表示第i天在两种状态下的最大收益

初始状态 就是第一天买入和不买入的两种情况

状态转移 每一天的两种状态都由前一天的两种状态决定

终止状态 一定是最后一天不持有股票的状态 

class Solution {
   public int maxProfit(int[] prices) {
        /*
        动态规划
        对于每一天都可能是两种状态 持有现金 或者持有股票
        持有现金 :前一天持有现金(理解成不持股票更好) 今天什么都不做 或者 前一天持有股票 今天卖出
        持有股票:前一天持有股票                      今天什么都不做 或者 前一天持有现金 今天买进
        dp[i][j] 表示第i天的最大利润 j取0代表持有现金 j取1代表持有股票
        
         */
        int len = prices.length;
        if (len == 0){
            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][j] 表示第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];
    }
}

123. 买卖股票的最佳时机 III

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

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

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

示例 1:

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1] 
输出: 0 
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
    // 这动态规划太难了 还是原来的方法 只是重新做一遍
    public int maxProfit(int[] prices) {
        /*
        五种状态
         */
        int len = prices.length;
        if (len == 0){
            return 0;
        }
        int max = prices[0];
        for (int i = 0;i < len;i++){
            if (prices[i] > max){
                max = prices[i];
            }
        }
        int[][] dp = new int[len][5];
        //初始化
        dp[0][1] = -1 * prices[0];
        // 因为在状态转移里一直在取最大值 所以先把还没发生的事情设置成最小值 也就不可能取到了
        // 不可能发生的事情:比如第0天不可能发生234状态,第一天不可能发生34状态, 把不可能发生的事当成0去算是不对的
        for (int i = 0;i < len;i++){
            dp[i][2] = Integer.MIN_VALUE + max;
            dp[i][3] = Integer.MIN_VALUE + max;
            dp[i][4] = Integer.MIN_VALUE + max;
        }
        for (int i = 1; i < len;i++){
            /*
                            0        1           2          3          4
            五个状态分别是 没买呢->第一次买进->第一次卖出->第二次买进->第二次卖出
            五个状态是递进的关系 每一天的状态 可以来自于前一天状态的延续 或是 前一天状态的递进
             */
            dp[i][0] = 0;
            dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] - prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2],dp[i - 1][1] + prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3],dp[i - 1][2] - prices[i]);
            dp[i][4] = Math.max(dp[i - 1][4],dp[i - 1][3] + prices[i]);
        }
        return Math.max(Math.max(dp[len - 1][0],dp[len - 1][2]),dp[len - 1][4]);

    }
}

188. 买卖股票的最佳时机 IV

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

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

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

示例 1:

输入: [2,4,1], k = 2
输出: 2
解释: 在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
     随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

解题思路 

只需要在上一题的基础上稍作改变 就能写出如下代码 但是在提交的时候 最后两个用例提示超内存 

分析状态转移  可以看到每一天的状态只和前一天的状态相关 那么可以压缩dp 用两行 记录当前状态和前一天状态 滚动进行

class Solution {
    public int maxProfit(int k, int[] prices) {
        /*
        k种状态 在两种状态下稍加改变
         */
        int len = prices.length;
        if (len == 0 || k == 0){
            return 0;
        }
        int max = prices[0];
        for (int i = 0;i < len;i++){
            if (prices[i] > max){
                max = prices[i];
            }
        }
        int state = 2 * k + 1;
        int[][] dp = new int[len][state];

        dp[0][1] = -1 * prices[0];
        for (int i = 0;i < len;i++){
            for (int j = 2; j < state; j++) {
                dp[i][j] = Integer.MIN_VALUE + max;
            }
        }
        for (int i = 1; i < len;i++){
            for (int j = 1; j < state; j++) {
                // 指导思想就是 每一天的状态 可以来自于前一天状态的延续 或是 前一天状态的递进
                if (j % 2 != 0){ //(j + 1)/2次买入
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] - prices[i]);
                }else{ //第j/2次卖出
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] + prices[i]);
                }
            }
        }
        int res = dp[len - 1][0];
        for (int j = 0;j < state;j++){
            if (dp[len - 1][j] > res){
                res = dp[len - 1][j];
            }
        }
        return res;
    }
}

多次修改版 -- 修改过程见注释

其实我并不理解leetcode为啥要给这么变态的用例。

class Solution {
   public int maxProfit(int k, int[] prices) {
        /*
        k种状态 在两种状态下稍加改变 
        第一次提交 两个用例超内存没通过 由于今天的状态只和昨天的状态有关系 进行状态压缩之后 也就是只剩两列
        第二次提交 还是超内存 用例中k特别特别大 甚至都大于len 因为一次交易就需要两天 天数有限 完不成那么特别                   多次的交易  所以对于k也要进行压缩 
        第三次提交 时间超时 再次修改 当k大于len/2的时候 可以用贪心法了 成功
         */
        int len = prices.length;
        if (len == 0 || k == 0){
            return 0;
        }
        /*
        //压缩k 哎 没用
        if (2 * k + 1 > len){ 
            k = len/2;
        }
        */
        int res = 0;
        // 超级大用例的特殊处理 贪心
        if (2 * k + 1 > len){
           for (int i = 1;i < len;i++){
               if (prices[i] > prices[i - 1]){
                   res += (prices[i] - prices[i - 1]);
               }
           }
           return res;
        }
        int max = prices[0];
        for (int i = 0;i < len;i++){
            if (prices[i] > max){
                max = prices[i];
            }
        }
        int state = 2 * k + 1;
        int[][] dp = new int[2][state];

        dp[0][1] = -1 * prices[0];
        for (int i = 0;i < 2;i++){
            for (int j = 2; j < state; j++) {
                dp[i][j] = Integer.MIN_VALUE + max;
            }
        }
        for (int i = 1; i < len;i++){
            for (int j = 1; j < state; j++) {
                //  1 代表计算的今天的状态 0是昨天的状态
                if (j % 2 != 0){ //(j + 1)/2次买入
                    dp[1][j] = Math.max(dp[0][j],dp[0][j - 1] - prices[i]);
                }else{ //第j/2次卖出
                    dp[1][j] = Math.max(dp[0][j],dp[0][j - 1] + prices[i]);
                }
                for(int m = 1;m < state;m++){ //今天会成为下一次循环的昨天
                    dp[0][m] = dp[1][m];
                }
            }
        }
        for (int j = 0;j < state;j++){
            if (dp[1][j] > res){
                res = dp[1][j];
            }
        }
        return res;
    }
}

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

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

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

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

示例:

输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

解题思路

动态规划的关键 是如何定义状态 和 状态转移的过程 

本题一开始我总是纠结冷冻期的状态和不持股的状态应该怎样理解和转换,后来舍弃了冷冻期这样的状态描述,看注释吧

class Solution {
   public int maxProfit(int[] prices) {
        /*
                                   0        1       2
        三种状态 每天都有三种状态 持有股票 冷冻期 不持有股票
        对于每天状态的定义 是指当天处在这种状态 可能是延续的前一天的 也可能是前一天的递进
        这里对于不持股的理解 是指当天卖出
        第i天是冷冻状态的条件是i - 1天是不持股
        这个状态转移我画不出来 整不明白状态
         */

        int len = prices.length;
        if (len == 0){
            return 0;
        }
        int[][] dp = new int[len][3];
        dp[0][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] = dp[i - 1][2];
            dp[i][2] = Math.max(dp[i - 1][2],dp[i - 1][0] + prices[i]);
            */

            /*
            看了一个题解是这样的,它不纠结怎样定义冷冻,它是根据冷冻规则产生的限制定义了这样三个状态
            0 :持股,定义其最大收益dp[i][0];
            1 :不持股且不是当天卖出的,定义其最大收益dp[i][1];
            2 :不持股且是当天卖出的,定义其最大收益dp[i][2];
            这样设置状态和状态转移就很清楚
            那么状态转移应该是
            前一天   今天
            0 --> 0,  2
            1 --> 0 , 1
            2 --> 1
            */
            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[len - 1][1],dp[len - 1][2]);


    }
}

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

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

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

返回获得利润的最大值。

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

示例 1:

输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

注意:

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

做了一天的股票交易 从上午做到下午 这道题是中等难度 果然再在做了一堆变态股票之后再看这道题就异常可爱了

可能也是因为简单 对于状态和状态转移可以看的很清楚

class Solution {
   public int maxProfit(int[] prices, int fee) {
        int len = prices.length;
        if (len == 0){
            return 0;
        }
        int[][] dp = new int[len][2];
        /*
        状态:
            0 : 持股
            1 :不持股
        状态转移 :
            0 -> 卖出 -> 1
            0 -> 啥都不做 -> 0
            1 -> 买入 -> 0
            1 -> 啥都不做 -> 1

         */
        dp[0][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] - fee);
        }
        return dp[len - 1][1];

    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涵sue

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值