代码随想录算法训练营第四十九天|121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

代码随想录算法训练营第四十九天|121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

买卖股票的最佳时机

121. 买卖股票的最佳时机
文章讲解:https://programmercarl.com/0121.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.html
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
视频讲解:https://www.bilibili.com/video/BV1Xe4y1u77q/

自己看到题目的第一想法

看完代码随想录之后的想法

dp数组定义:

  • dp[i][0]:第i天,持有股票最大现金。
  • dp[i][1]:第i天,不持有股票的最大现金。
  • 最大值:Math.max(dp[length - 1][0],dp[length - 1][1])
  • 注意,这里是“持有”,不是第i天就买第i天的股票,可能i-1天就买了然后第i天只是持有这个i-1天买的股票。同理“不持有”,也不是指第i天卖了股票,而是第i天不持有,可能第i-1天卖的,可能也是第i天卖的。
  • 个人理解,一直用持有去算最大。同理,一直用不持有去算最大。

递推公式:

  • dp[i][0]第i天,持有股票最大现金的计算方式:可能第i-1天就买入了,因此可能价值为dp[i-1][0]。也可能是第i天买入的,第i天买入的话最大现金就-prices[i],又因为本题买卖就只有一次,因此dp[i][0]的递推公式即为 Math.max(dp[i-1][0],-prices[i]);
  • dp[i][1]第i天,不持有股票的最大现金的计算方式:可能第i-1天就不持有了,因此dp[i][1]可能的价值为dp[i-1][1];

确定初始化值:

  • 这里主要是初始化dp[0][0]和dp[0][1],dp[0][0]表示持有最大现金,第0天未买入的最大价值则为dp[0][0]=0;第0天买入的最大价值dp[0][1] = -prices[0];

如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来:

  • 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
  • 第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
    那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);

如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来:

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
    同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);

自己实现过程中遇到哪些困难

自己第一次实现的错误,在实现过程中,把递推公式,理解成第i天买入。而代码随想录的概念是第i天持有股票,或者不持有股票,这里指的是有没有股票,而不是第i天做买入、卖出的动作。
自己实现时的问题代码:

public int maxProfit(int[] prices) {
     // 动态规划五步曲
     // 1. 确定dp数组定义,dp[i][0]和dp[i][1],dp[i][0]表示第i天时,不持有第i天股票的最大价值。dp[i][1]表示第i天,持有第i天股票的最大价值
     int[][] dp = new int[prices.length][prices.length];
     // 2. 确定递推公式 
     // 2.1 第i天时不持有第i天股票的价值和前一天的价值一样 dp[i][0] = dp[i-1][0];
     // 2.2 第i天持有第i天股票的价值为 dp[i][1] = -prices[i]。因为这里只能买卖一次
     
     // 3. 确定初始值
     dp[0][0] = 0;
     dp[0][1] = 0 - prices[0];
     // 4. 确定遍历顺序,因为值是从i-1推到i的,因此从小到大遍历。
     for(int i = 1; i < prices.length; i++){
         dp[i][0] = dp[i-1][0];
         dp[i][1] = 0 - prices[i];
     }
     return Math.max(dp[prices.length - 1][0],dp[prices.length - 1][1]);

 }

修改后的代码:

public int maxProfit(int[] prices) {
    // 动态规划五步曲
    // 1. 确定dp数组定义,dp[i][0]和dp[i][1],dp[i][0]表示第i天时,不持有第i天股票的最大价值。dp[i][1]表示第i天,持有第i天股票的最大价值
    int[][] dp = new int[prices.length][prices.length];
    // 2. 确定递推公式 
    // 第i天持有股票即dp[i][0],可能之前就持有了,可能是第i天买入:Math.max(dp[i - 1][0], -prices[i]);
    // 第i天不持有股票即dp[i][1],可能是之前就不持有了,可能是第i天卖掉:Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
    
    // 3. 确定初始值
    dp[0][0] -= prices[0];
    dp[0][1] = 0;
    // 4. 确定遍历顺序,因为值是从i-1推到i的,因此从小到大遍历。
    for(int i = 1; i < prices.length; i++){
        // 第i天持有股票即dp[i][0],可能之前就持有了,可能是第i天买入
        dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
        // 第i天不持有股票即dp[i][1],可能是之前就不持有了,可能是第i天卖掉
        dp[i][1] = Math.max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
    }
    return dp[prices.length - 1][1];
}

买卖股票的最佳时机II

122.买卖股票的最佳时机II
文章讲解:https://programmercarl.com/0122.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAII%EF%BC%88%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%EF%BC%89.html
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
视频讲解:https://www.bilibili.com/video/BV1D24y1Q7Ls/

自己看到题目的第一想法

和121. 买卖股票的最佳时机的题目类似,但是该题目有了多次的买与卖,121题只有一次买卖。最大的区别就是买入和卖出最大值都需要关心上一天的值。

看完代码随想录之后的想法

买入和卖出最大值都需要关心上一天的值。

自己实现过程中遇到哪些困难

自己实现这里递推公式处理错了,自己实现的错误代码:

public int maxProfit(int[] prices) {
    if(prices.length == 1){
        return 0;
    }
    // 动态规划五步骤
    // 1. 确定dp定义 dp[i][0]:第i天,未持有股票的最大价值;dp[i][1],第i天,持有股票的最大价值
    int[][] dp = new int[prices.length][prices.length];
    // 2. 确定推导公式 
    // 2.1 第i天,未持有股票,可能之前就一直不持有dp[i][0] = dp[i - 1][0] ,也可能第i天卖了,dp[i][0] = dp[i - 1][0] + prices[i]
    // 2.2 第i天,持有股票,可能之前就一直持有dp[i][1] = dp[i-1][1],也可能第i天买入,dp[i - 1][1] - prices[i]
    // 3. 确定初始化值
    dp[0][0] = 0;
    dp[0][1] -= prices[0];

    // 4. 确定遍历顺序(这里错误)
    for(int i = 1; i < prices.length; i++){
        dp[i][0] = Math.max(dp[i-1][0],dp[i-1][0] + prices[i]);
        dp[i][1] = Math.max(dp[i-1][1],dp[i-1][1] - prices[i]);
    }
    return dp[prices.length - 1][0];
}

这里的推导公式应该要考虑到上一节点也会做买卖。
当买入股票时,可能会有之前买卖的利润,因此最大值的话用上一节点不持有股票的最大价值来计算才会是最大值。因此 dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i]);
同不持有股票时,存在卖和不卖的情况,卖的情况要考虑到上一节点持有股票时的最大利润做加法 dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]);

今日收获&学习时长

今日学习1h,学习了下股票最佳时机。核心的逻辑就是要使用持有时的最大价值和不持有时的最大价值来计算。使用二维数组+动态规划五步骤做逻辑处理。

你可能感兴趣的:(算法)