【代码随想录|刷题记录Day50】123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

89 阅读5分钟

题目列表

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

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

解题过程

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

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

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

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

思路: 需要注意的是这里最多可以完成两笔交易。

动态规划五部曲:

  • 确定dp数组以及下标的含义(一天有五个状态)
    • 0:没有操作
    • 1:第一次持有股票
    • 2:第一次不持有股票
    • 3:第二次持有股票
    • 4:第二次不持有股票
    • dp[i][j]表示第i天,状态为j所剩最大现金。
  • 确定递推公式
    • dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])
      • 第i天买入股票,dp[i][1] = dp[i - 1][0] - prices[i]
      • 第i天没有操作,沿用前一天买入的状态,dp[i - 1][1]
    • dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
    • dp[i][3] = max(dp[i - 1][2] - prices[i], dp[i - 1][3])
    • dp[i][4] = max(dp[i - 1][3] + prices[i], dp[i - 1][4])
  • dp数组初始化
    • dp[0][0] = 0
    • dp[0][1] = - prices[0]
    • dp[0][2] = 0
    • dp[0][3] = - prices[0]
    • dp[0][4] = 0
  • 确定遍历顺序
    • 从前向后遍历,dp[i]依赖dp[i - 1]的数值
  • 举例推导dp数组
class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) {
            return 0;
        }
        int length = prices.length;
        int[][] dp = new int[length][5];
        dp[0][1] = - prices[0];
        dp[0][3] = - prices[0];
        for (int i = 1; i < length; i++) {
            dp[i][1] = Math.max(- prices[i], dp[i - 1][1]);
            dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
            dp[i][3] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][3]);
            dp[i][4] = Math.max(dp[i - 1][3] + prices[i], dp[i - 1][4]);
        }
        return dp[length - 1][4];
    }
}

一维dp数组

// 版本二: 空间优化
class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[4]; 
        // 存储两次交易的状态就行了
        // dp[0]代表第一次交易的买入
        dp[0] = -prices[0];
        // dp[1]代表第一次交易的卖出
        dp[1] = 0;
        // dp[2]代表第二次交易的买入
        dp[2] = -prices[0];
        // dp[3]代表第二次交易的卖出
        dp[3] = 0;
        for(int i = 1; i <= prices.length; i++){
            // 要么保持不变,要么没有就买,有了就卖
            dp[0] = Math.max(dp[0], -prices[i-1]);
            dp[1] = Math.max(dp[1], dp[0]+prices[i-1]);
            // 这已经是第二次交易了,所以得加上前一次交易卖出去的收获
            dp[2] = Math.max(dp[2], dp[1]-prices[i-1]);
            dp[3] = Math.max(dp[3], dp[2]+ prices[i-1]);
        }
        return dp[3];
    }
}

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

给定一个整数数组 prices ,它的第 **i 个元素 prices[i] 是一支给定的股票在第 i **天的价格,和一个整型 k

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

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

思路: 递推和初始化的地方要类比j为偶数是卖、奇数是买的状态。

// 版本一: 三维 dp数组
class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length == 0) return 0;

        // [天数][交易次数][是否持有股票]
        int len = prices.length;
        int[][][] dp = new int[len][k + 1][2];
        
        // dp数组初始化
        // 初始化所有的交易次数是为确保 最后结果是最多 k 次买卖的最大利润
        for (int i = 0; i <= k; i++) {
            dp[0][i][1] = -prices[0];
        }

        for (int i = 1; i < len; i++) {
            for (int j = 1; j <= k; j++) {
                // dp方程, 0表示不持有/卖出, 1表示持有/买入
                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[len - 1][k][0];
    }
}

// 版本二: 二维 dp数组
class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length == 0) return 0;

        // [天数][股票状态]
        // 股票状态: 奇数表示第 k 次交易持有/买入, 偶数表示第 k 次交易不持有/卖出, 0 表示没有操作
        int len = prices.length;
        int[][] dp = new int[len][k*2 + 1];
        
        // dp数组的初始化, 与版本一同理
        for (int i = 1; i < k*2; i += 2) {
            dp[0][i] = -prices[0];
        }

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

//版本三:一维 dp数组
class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length == 0){
            return 0;
        }
        if(k == 0){
            return 0;
        }
        // 其实就是123题的扩展,123题只用记录2次交易的状态
        // 这里记录k次交易的状态就行了
        // 每次交易都有买入,卖出两个状态,所以要乘 2
        int[] dp = new int[2 * k];
        // 按123题解题格式那样,做一个初始化
        for(int i = 0; i < dp.length / 2; i++){
            dp[i * 2] = -prices[0];
        }
        for(int i = 1; i <= prices.length; i++){
            dp[0] = Math.max(dp[0], -prices[i - 1]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]);
            // 还是与123题一样,与123题对照来看
            // 就很容易啦
            for(int j = 2; j < dp.length; j += 2){
                dp[j] = Math.max(dp[j], dp[j - 1] - prices[i-1]);
                dp[j + 1] = Math.max(dp[j + 1], dp[j] + prices[i - 1]);
            }
        }
        // 返回最后一次交易卖出状态的结果就行了
        return dp[dp.length - 1];
    }
}

总结

困难的股票买卖问题...代码二刷再看吧,脑子转不过来了...