代码随想录训练营第50天|LeetCode 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

参考

代码随想录

题目一:LeetCode 123.买卖股票的最佳时机III

  1. 确定dp数组下标及其含义
    某一天最多存在5个状态:
    • j = 0:没有操作
    • j = 1:第一次买入
    • j = 2:第一次卖出
    • j = 3:第二次买入
    • j = 4:第二次卖出

因此dp[i][j]定义为第i天j状态下所剩的最大现金。

  1. 确定递推公式
  • dp[i][0]
    第i天没有操作,那么其状态和第i-1天一样,即dp[i][0] = d0[i-1][0].
  • dp[i][1]
    注意,dp[i][1]表示的含义是第i天保持第一次买入这个状态所剩的最大现金,并不一定是第i天买入,也可能在之前就买入了。如果第i天买入,那么dp[i][1] = dp[i-1][0]-prices[i];如果在此之前就已经买入,那么dp[i][1] = dp[i-1][1]。最后要保证剩余金额最大(即买股票花费最少),因此dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i]).
  • dp[i][2]
    dp[i][2]表示第i天在第一次卖出这个状态下所剩余的最大现金。如果是在第i天卖出,那么dp[i][2] = dp[i-1][1] + prices[i];如果在i天之前就已经卖出,那么dp[i][2] = dp[i-1][2]。为保证利润最大,dp[i][2] = max(dp[i-1][2],dp[i-1][1] + prices[i])。
  • dp[i][3]
    dp[i][3]表示第i天保持第2次买入这个状态所剩余的最大现金。如果在第i天第二次买入,那么dp[i][3] = dp[i-1][2] - prices[i];如果在此之前已经买入,那么dp[i][3] = dp[i-1][3],为保证剩余现金最大,dp[i][3] = max(dp[i-1][3],dp[i-1][2] - prices[i])。
  • dp[i][4]
    dp[i][4]表示第i天保持第二次卖出状态下所剩余的最大现金。如果在第i天卖出,那么dp[i][4] = dp[i-1][3] + prices[i];如果在此之前已经卖出,那么dp[i][4] = dp[i-1][4]。为保证最终的利润最大,dp[i][4] = max(dp[i-1][4],dp[i-1][3] + prices[i]).

最终的递推公式为:

dp[i][0] = dp[i-1][0];
dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i]);
dp[i][2] = max(dp[i-1][2],dp[i-1][1] + prices[i]);
dp[i][3] = max(dp[i-1][3],dp[i-1][2] - prices[i]);
dp[i][4] = max(dp[i-1][4],dp[i-1][3] + prices[i]);
  1. dp数组初始化
    dp数组的第i状态只涉及到第i-1状态,因此只需要初始化i = 0的状态即可。
  • dp[0][0] = 0,第0天没有任何操作,那么剩余的现金为0,这是自定义的,可以定义任意的剩余现金,在最后计算利润的时候减去初始的剩余现金即可,如果初始化为0,最终剩余的现金就是最终利润。
  • dp[0][1] = -prices[0],第0天买入后剩余的现金就是 -prices[0],因为在上面已经初始化第0天持有的现金为0
  • dp[0][2] = 0,可以理解为第0天买入后又在第0天卖出,所以持有的剩余现金为0
  • dp[0][3] = -prices[0],第0天第二次买入,可以理解为第0天第一次买入后卖出,再买入
  • dp[0][4] = 0,第0天第一次买入再卖出,再第二次买入再卖出

总结起来,初始化如下:

dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
dp[0][3] = -prices[0];
dp[0][4] = 0;
  1. 确定遍历i顺序
    第i天的状态由第i-1天来确定,因此要从前往后遍历。

  2. 举例推导dp数组
    代码随想录训练营第50天|LeetCode 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV_第1张图片
    可以看出,最后第二次卖出的利润是要大于等于第一次卖出的利润的,所以最终返回第二次卖出所得的利润即可。
    完整的代码实现如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(prices.size(),vector<int>(5));
        //初始化dp数组
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;
        dp[0][3] = -prices[0];
        dp[0][4] = 0;
        //遍历
        for(int i = 1; i < dp.size(); i++){
            dp[i][0] = dp[i-1][0];
            dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i]);
            dp[i][2] = max(dp[i-1][2],dp[i-1][1] + prices[i]);
            dp[i][3] = max(dp[i-1][3],dp[i-1][2] - prices[i]);
            dp[i][4] = max(dp[i-1][4],dp[i-1][3] + prices[i]);
        }
        return dp.back()[4];
    }
};

题目二:LeetCode 188.买卖股票的最佳时机IV

这个题在上一个题的基础上加最多买卖两次变成最多买卖k次,其实求解思路是一样的,在上一个题推导递推公式的时候其实就可以发现规律了。
上一个题最多买卖两次,每天最多有5个状态,其中一个是没有操作,剩余4个是分别是每次交易的两个状态,因此很容易得到最多k次交易时,每天有2k+1个状态,其中的1是没有操作,其余每次交易都有两个状态。同样用j来标记每天的状态,当j % 2 == 1时表示当前是第j/2+1次买入状态,当j % 2 == 0时表示第j/2次卖出状态,这里当k == 2带入就是上一个题的情况了。
因此,对于最多k次交易,定义dp[i][j]:第i天时j状态下所剩余的最大现金为dp[i][j],其中0 <= j <= 2*k,当j为奇数时表示买入,当j为偶数时表示卖出。
如果是买入(j为奇数)状态,则递推公式为:

dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]);

如果是卖出(j为偶数)状态,则递推公式为:

dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + prices[i]);

完整的代码实现如下:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        vector<vector<int>> dp(prices.size(),vector<int>(2 * k + 1));
        //初始化dp数组
        for(int j = 1 ; j < 2 * k + 1; j += 2) //注意这里是j += 2
            dp[0][j] = -prices[0];  //买入状态初始化,卖出状态已经初始化为0
        //遍历
        for(int i = 1; i < dp.size(); i++){
            //dp[i][0] = dp[i-1][0];  //始终保持不变,可以不用
            for(int j = 1; j < 2 * k + 1; j += 2){      //注意这里是j += 2
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]);    //买入
                dp[i][j+1] = max(dp[i-1][j+1],dp[i-1][j] + prices[i]);  //卖出
            }
        }
        return dp.back()[2*k];
    }
};

你可能感兴趣的:(代码随想录训练营,leetcode,算法,动态规划)