【dp】买卖股票的最佳时机系列题目

文章目录

    • 121. 买卖股票的最佳时机
    • 122. 买卖股票的最佳时机 II
    • 309. 最佳买卖股票时机含冷冻期
    • 123. 买卖股票的最佳时机 III
    • 188. 买卖股票的最佳时机 IV

121. 买卖股票的最佳时机

【dp】买卖股票的最佳时机系列题目_第1张图片
本题的重点是:只能在前面某一天买入,后面某一天卖出。要不就是不买入,收益为0

dp数组含义

本题两个状态:持有股票、不持有股票

  • dp[i][1] :表示第i天持有股票所得最多现金
  • dp[i][0] :表示第i天不持有股票所得最多现金

最后返回dp[n-1][0],表示最后一天不持有股票,所获得最多的现金

递推公式

第i天不持有股票所得最多现金 = max(第i-1天不持有股票手上的现金, 第i-1天手上持有股票、但是在第i天卖出手上的现金)

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

第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金, 第i-1天手上没有股票、但是在第i天买入股票手上的现金)

dp[i][1] = max(dp[i-1][1], 0 - prices[i])    // i-1天不持有,第i天持有,那一定是第i天买入

初始化

  • dp[0][0]:第0天不持有股票,所能获得的现金,就是0
  • dp[0][1]:第0天持有股票,所能获得的现金,就是第0天买入股票后手上的钱,即 -prices[0]
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        // dp[i][1] 表示第i天持有股票所得最多现金
        // dp[i][0] 表示第i天不持有股票所得最多现金
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][1] = 0 - prices[0];    // 第0天持有股票,手上的钱就是买入后的负数
        dp[0][0] = 0;                // 第0天不持有股票,手上的钱是0    
        for(int i = 1; i < n; i++){
            // 第i天不持有股票所得最多现金 = max(第i-1天没有股票手上的现金,  第i-1天手上持有股票、但是在第i天卖出手上的现金)
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
            // 第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金,  第i-1天手上没有股票、但是在第i天买入股票手上的现金)
            dp[i][1] = max(dp[i-1][1], 0 - prices[i]);           
        }
        return dp[n-1][0];
    }
};

第二种dp方法

第i天的最大收益 = max(第i-1天的最大收益,第i天的价格 - 前i-1天的最低价)

dp[i] = max(dp[i-1], prices[i] - min_val)
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(n, 0);   // dp[i]:第i天的最大收益
        dp[0] = 0;
        int min_val = prices[0];
        for(int i = 1; i < n; i++){
            min_val = min(min_val, prices[i]);
            dp[i] = max(dp[i-1], prices[i] - min_val);
        }
        return dp[n-1];
    }
};

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

【dp】买卖股票的最佳时机系列题目_第2张图片

本题的重点是:可以在任意一天进行买入或卖出,且不限制买入和卖出的次数

dp数组含义

本题两个状态:持有股票、不持有股票

  • dp[i][1] :表示第i天持有股票所得最多现金
  • dp[i][0] :表示第i天不持有股票所得最多现金

最后返回dp[n-1][0],表示最后一天不持有股票,所获得最多的现金

递推公式

第i天不持有股票所得最多现金 = max(第i-1天不持有股票手上的现金, 第i-1天手上持有股票、但是在第i天卖出手上的现金)

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

第i天持有股票手上的现金 = max(第i-1天持有股票手上的现金, 第i-1天手上没有股票、但是在第i天买入股票手上的现金)

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

初始化

  • dp[0][0]:第0天不持有股票,所能获得的现金,就是0
  • dp[0][1]:第0天持有股票,所能获得的现金,就是第0天买入股票后手上的钱,即 -prices[0]
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        // dp[i][1] 表示第i天持有股票手上最多的现金
        // dp[i][0] 表示第i天不持有股票所得最多现金
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][1] = 0 - prices[0];
        dp[0][0] = 0;
        // dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
        // dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
        for(int i = 1; i < n; i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
        }
        return dp[n-1][0];
    }
};

直接把价格上升的部分累加即可
【dp】买卖股票的最佳时机系列题目_第3张图片

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int ans = 0;
        for(int i = 1; i < n; i++){
            if(prices[i] > prices[i-1]) ans += prices[i] - prices[i-1];
        }
        return ans;
    }
};

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

【dp】买卖股票的最佳时机系列题目_第4张图片

本题就是在122. 买卖股票的最佳时机 II的基础上加上了冷冻期

122. 买卖股票的最佳时机 II有两个状态:持有股票后的最多现金,和不持有股票的最多现金

而本题中,我们将状态分为三种:持有股票、不持有股票处于冷冻期、不持有股票不处于冷冻期

dp数组含义

  • dp[i][0]:不持有股票,不在冷冻期,能获得的最大收益
  • dp[i][1]:不持有股票,在冷冻期,能获得的最大收益
  • dp[i][2]:持有股票,能获得的最大收益

【dp】买卖股票的最佳时机系列题目_第5张图片

递推公式

第i天不持有股票,且不在冷冻期能获得的最大收益 = max(前一天处于冷冻期的收益,前一天不持有股票且不在冷冻期的收益)

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

第i天不持有股票,且在冷冻期能获得的最大收益 = 前一天持有股票卖出的收益

dp[i][1] = dp[i-1][2] + prices[i]

第i天持有股票能获得的最大收益 = max(前一天持有股票没卖,前一天不持有股票且不在冷冻期但是买入股票)

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

初始化

  • dp[0][0] = 0:不持有股票,不在冷冻期
  • dp[0][1] = 0:不持有股票,在冷冻期
  • dp[0][2] = 0 - prices[0]:持有股票,第0天买入即可
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(3, 0));
        dp[0][0] = 0;  // 不持有股票,不在冷冻期
        dp[0][1] = 0;  // 不持有股票,在冷冻期
        dp[0][2] = 0 - prices[0];  // 持有股票
        
        for(int i = 1; i < n; i++){
            dp[i][0] = max(dp[i-1][1], dp[i-1][0]);
            dp[i][1] = dp[i-1][2] + prices[i];
            dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i]);
        }
        return max(dp[n-1][0], dp[n-1][1]);
    }
};

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

【dp】买卖股票的最佳时机系列题目_第6张图片
一天一共就有五个状态:

  • 未操作过
  • 处于第一次持有股票的状态
  • 处于第一次持有股票、又卖出的状态
  • 处于第二次持有股票的状态
  • 处于第二次不持有股票、又卖出的状态

dp数组含义

dp[i][j]:表示第i天,处于状态j时,所能获得的最大收益

  • dp[i][0]:没买卖过股票,所能获得的最大收益
  • dp[i][1]:处于第一次持有股票的状态,所能获得的最大收益
  • dp[i][2]:处于第一次持有股票、又卖出的状态,所能获得的最大收益
  • dp[i][3]:处于第二次持有股票的状态,所能获得的最大收益
  • dp[i][4]:处于第二次不持有股票、又卖出的状态,所能获得的最大收益

【dp】买卖股票的最佳时机系列题目_第7张图片

递推公式

状态0:没买卖过股票

很明显,图中没有入度,无需更新,所能获得的最大收益保持为0

状态1:第一次持有股票

第一次处于持有股票状态的最大收益 = max(前一天就处于第一次持有股票状态, 前一天没做操作、今天才买入股票)

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

状态2:第一次持有股票、又卖出

第一次处于持有股票、又卖出状态的最大收益 = max(前一天就处于第一次持有股票、又卖出状态, 前一天处于持有股票、今天才卖出)

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

状态3:第二次持有股票

第二次处于持有股票状态的最大收益 = max(前一天就处于第二次持有股票状态, 前一天处于第一次持有股票并卖出、今天才买入股票)

dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])

状态4:第二次不持有股票、又卖出

第二次处于持有股票、又卖出状态的最大收益 = max(前一天就处于第二次持有股票、又卖出状态, 前一天处于第二次持有股票、今天才卖出)

dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])

初始化

  • dp[0][0]:第0天没操作,收益为0
  • dp[0][1]:第0天买入,收益为-prices[0]
  • dp[0][2]:第0天买入后卖出,收益为0
  • dp[0][3]:第0天买入后卖出,再买入,收益为-prices[0]
  • dp[0][4]:第0天买入后卖出,再买入卖出,收益为0
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(5, 0));
        dp[0][1] = 0 - prices[0];
        dp[0][3] = 0 - prices[0];
        for(int i = 1; i < n; i++){
            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[n-1][4];
    }
};

观察可以发现,二维数组中,计算当前层数据时,只需要使用上方元素,以及上方相邻元素即可,和背包问题压缩维度原理一样,本题也可以压缩维度,逆序更新即可。因为计算下一行时,没有用到后面的元素,先更新后面的没有影响

其实本题不改变更新顺序也行

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(5, 0);
        dp[1] = 0 - prices[0];
        dp[3] = 0 - prices[0];
        for(int i = 1; i < n; i++){
        	dp[4] = max(dp[4], dp[3] + prices[i]);
        	dp[3] = max(dp[3], dp[2] - prices[i]);
            dp[2] = max(dp[2], dp[1] + prices[i]);
            dp[1] = max(dp[1], dp[0] - prices[i]);   
        }
        return dp[4];
    }
};

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

【dp】买卖股票的最佳时机系列题目_第8张图片

上题中,最多两笔交易时,有5个状态;本题最多k笔交易,会有2*k+1个状态,因为完成k笔交易,需要k次买入和k次卖出,再加上不做任何操作的状态,总共有2*k+1个状态

和上题一样,我们把除了0以外的奇数状态定为持有股票状态,而偶数状态定为卖出股票的状态

dp数组含义

dp[i][j]:表示第i天,处于状态j时,所能获得的最大收益,强调一下,第i天可以经过多种状态,因为不限制每天卖卖股票的次数,就像下面初始化时所描述的一样

初始化

  • dp[0][0]:第0天没操作,收益为0
  • dp[0][1]:第0天买入,收益为-prices[0]
  • dp[0][2]:第0天买入后卖出,收益为0
  • dp[0][3]:第0天买入后卖出,再买入,收益为-prices[0]
  • dp[0][4]:第0天买入后卖出,再买入卖出,收益为0

我们可以看出,奇数状态为买入状态,统一初始化为-prices[0],偶数状态为0

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2 * k + 1, 0));
        for(int i = 1; i < 2 * k + 1; i += 2){
            dp[0][i] = 0 - prices[0];
        }
        for(int i = 1; i < n; i++){
            for(int j = 1; j < 2 * k + 1; j++){
                if(j & 1) dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i]);
                else dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + prices[i]);
            }
        }
        return dp[n-1][2*k];
    }
};

压缩维度

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(2 * k + 1, 0);
        for(int i = 1; i < 2 * k + 1; i += 2){
            dp[i] = 0 - prices[0];
        }
        for(int i = 1; i < n; i++){
            for(int j = 2 * k; j > 0; j--){
                if(j & 1) dp[j] = max(dp[j], dp[j-1] - prices[i]);
                else dp[j] = max(dp[j], dp[j-1] + prices[i]);
            }
        }
        return dp[2*k];
    }
};

你可能感兴趣的:(剑指offer,算法,动态规划,leetcode)