Day49【动态规划】121.买卖股票的最佳时机、122.买卖股票的最佳时机II

121.买卖股票的最佳时机

力扣题目链接/文章讲解

视频讲解

动态规划五部曲!

1、确定 dp 数组下标及值的含义 

先想想本题 dp 应该怎么定义,别忘了之前说的,dp 数组的下标能够表示状态

在股票问题中,某个状态需要描述在某天,及是否持有股票

因此我们定义 dp 数组下标及值含义:

dp[i][0]:下标表示在第 i 天,未持有股票,值表示第 i 天未持有股票所得最多现金

dp[i][1]:下标表示在第 i 天,持有股票,值表示第 i 天持有股票所得最多现金

通过第一个下标 i 描述在哪一天,第二个下标为 0 或 1 描述在该天未持有或持有股票,这样通过两个下标就能够描述所有的状态了

2、确定递推公式

分别思考 dp[i][0] 和 dp[i][1] 分别应该怎么推

dp[i][0]:需要求第 i 天未持有股票所得最多现金,要考虑怎么转移到第 i 天未持有股票的这种状态

  • 可能是在第 i 天卖出了股票,所得现金就是昨天持有股票,然后今天卖出股票后所得现金,即:dp[i - 1][1](昨天持有股票)+ prices[i](今天卖出股票)
  • 可能是在第 i - 1 天就未持有股票,那么就保持现状,所得现金就是昨天未持有股票的所得现金,即:dp[i - 1][0](昨天未持有股票)

因为求的最多现金,即 dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]) 

同理,dp[i][1]:

  • 可能是在第 i 天买入股票,所得现金为 -prices[i](注意本题要求股票全程只能买一次,所以如果买入股票,买之前的现金一定为 0,买之后的现金一定为 -prices[i]
  • 可能是在第 i - 1 天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金,即:dp[i - 1][1](昨天持有股票)

因为求的最多现金,即 dp[i][1] = max(-prices[i], dp[i - 1][1])  

3、dp 数组初始化

从递推公式看出,求某一天 dp 需要知道其前一天的 dp 值,即都是要从dp[0][0]和dp[0][1]推导出来

那么dp[0][0]表示第 0 天未持有股票,不持有股票现金就是 0,所以 dp[0][0] = 0

dp[0][1]表示第 0 天持有股票,持有股票一定是在第 0 天买入了股票,所以 dp[0][1] = -prices[0]

4、确定遍历顺序

从递推公式可以看出 dp[i] 都是由 dp[i - 1] 推导出来的,那么一定是从前向后遍历

5、打印 dp 数组验证

代码如下:

class Solution {
public:
    int maxProfit(vector& prices) {

        if (prices.size() == 0 || prices.size() == 1) return 0;
        
        // 定义dp数组下标及值含义
        vector > dp(prices.size(), vector(2));
        
        // 确定递推公式:dp[i][0]和dp[i][1]分别确定

        // dp数组初始化,仅初始化第一天即可
        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        // 从前往后遍历,一行一行填充
        for (int i = 1; i < prices.size(); ++i) {
            dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
            dp[i][1] = max(-prices[i], dp[i - 1][1]);
        }

        // 最后一定卖了才能取得最大
        return dp[prices.size() - 1][0];
    }
};

从递推公式可以看出,dp[i] 只是依赖于 dp[i - 1] 的状态

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

可以使用滚动数组来节省空间,仅记录一天的 dp

Day49【动态规划】121.买卖股票的最佳时机、122.买卖股票的最佳时机II_第1张图片

如图所示,左边为原 dp 数组,右边是滚动数组。相当于就地更新原 dp 数组的每一行 

注意滚动数组中更新 dp 时的遍历顺序,需要从左向右遍历,这样更新 dp[0] 的时候才保证等式右边的 dp 值能对应到原二维数组中 i - 1 层的值

代码如下:

class Solution {
public:
    int maxProfit(vector& prices) {

        if (prices.size() == 0 || prices.size() == 1) return 0;

        // 滚动数组,仅记录原二维dp的一行
        int dp[2];

        // 初始化原二维dp数组的第一行
        dp[0] = 0;
        dp[1] = -prices[0];

        // 一行一行遍历填充原二维数组
        for (int i = 1; i < prices.size(); ++i) {   // 遍历天数
            dp[0] = max(dp[1] + prices[i], dp[0]);
            dp[1] = max(-prices[i], dp[1]);
        }   // 遍历填充完毕后,滚动数组记录的是原二维dp的最后一行的数据

        return dp[0];
    }
};

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

力扣题目链接/文章讲解

视频讲解

动态规划五部曲!

1、确定 dp 数组下标及值的含义 

先想想本题 dp 应该怎么定义,别忘了之前说的,dp 数组的下标能够表示状态

在股票问题中,某个状态需要描述在某天,及是否持有股票

因此我们定义 dp 数组下标及值含义:

dp[i][0]:下标表示在第 i 天,未持有股票,值表示第 i 天未持有股票所得最多现金

dp[i][1]:下标表示在第 i 天,持有股票,值表示第 i 天持有股票所得最多现金

通过第一个下标 i 描述在哪一天,第二个下标为 0 或 1 描述在该天未持有或持有股票,这样通过两个下标就能够描述所有的状态了

2、确定递推公式

分别思考 dp[i][0] 和 dp[i][1] 分别应该怎么推

dp[i][0]:需要求第 i 天未持有股票所得最多现金,要考虑怎么转移到第 i 天未持有股票的这种状态

  • 可能是在第 i 天卖出了股票,所得现金就是昨天持有股票,然后今天卖出股票后所得现金,即:dp[i - 1][1](昨天持有股票)+ prices[i](今天卖出股票)
  • 可能是在第 i - 1 天就未持有股票,那么就保持现状,所得现金就是昨天未持有股票的所得现金,即:dp[i - 1][0](昨天未持有股票)

因为求的最多现金,即 dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]) 

同理,dp[i][1]:

  • 可能是在第 i 天买入股票,所得现金为 dp[i - 1][0](昨天未持有股票)- prices[i](今天买入股票)(因为一只股票可以买卖多次,所以当第 i 天买入股票的时候,所持有的现金需要考虑之前买卖过的利润
  • 可能是在第 i - 1 天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金,即:dp[i - 1][1](昨天持有股票)

因为求的最多现金,即 dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])  

3、dp 数组初始化

从递推公式看出,求某一天 dp 需要知道其前一天的 dp 值,即都是要从dp[0][0]和dp[0][1]推导出来

那么dp[0][0]表示第 0 天未持有股票,不持有股票现金就是 0,所以 dp[0][0] = 0

dp[0][1]表示第 0 天持有股票,持有股票一定是在第 0 天买入了股票,所以 dp[0][1] = -prices[0]

4、确定遍历顺序

从递推公式可以看出 dp[i] 都是由 dp[i - 1] 推导出来的,那么一定是从前向后遍历

5、打印 dp 数组验证

代码如下:

class Solution {
public:
    int maxProfit(vector& prices) {

        if (prices.size() == 0 || prices.size() == 1) return 0;
        
        // 定义dp数组下标及值含义
        vector > dp(prices.size(), vector(2));
        
        // 确定递推公式:dp[i][0]和dp[i][1]分别确定

        // dp数组初始化,仅初始化第一天即可
        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        // 从前往后遍历,一行一行填充
        for (int i = 1; i < prices.size(); ++i) {
            dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
            dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
        }

        // 最后一定卖了才能取得最大
        return dp[prices.size() - 1][0];
    }
};

同样,能够通过滚动数组优化

Day49【动态规划】121.买卖股票的最佳时机、122.买卖股票的最佳时机II_第2张图片

为了更新 dp 时能够正确用到原二维 dp 中上一层的 dp 值,需要用到 temp 数组记录上一层的 dp 值 

class Solution {
public:
    int maxProfit(vector& prices) {

        if (prices.size() == 0 || prices.size() == 1) return 0;
        
        // 滚动数组仅需维护原dp数组一行(一天)的数据
        int dp[2];
        
        // dp数组初始化,仅初始化原dp数组第一行(第一天)即可
        dp[0] = 0;
        dp[1] = -prices[0];

        // 用于记录上一行(前一天)的数据
        int temp[2];

        // 一行一行遍历填充
        for (int i = 1; i < prices.size(); ++i) {
            
            // 储存二维dp上一行的数据
            temp[0] = dp[0];
            temp[1] = dp[1];
            
            // 更新二维dp本行的数据
            dp[0] = max(temp[1] + prices[i], temp[0]);
            dp[1] = max(temp[0] - prices[i], temp[1]);
        }

        // 最后一定卖了才能取得最大
        return dp[0];
    }
};

回顾总结 

关键是需要明确 dp 数组的含义:用第二维区分持有股票和不持有股票的状态

你可能感兴趣的:(代码随想录,动态规划,算法,leetcode,数据结构,c++)