代码随想录算法训练营DAY49|C++动态规划Part10|188.买卖股票的最佳时机IV、309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

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

力扣题目链接

文章链接:188.买卖股票的最佳时机IV

视频链接:动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4

状态:其实和题目123.买卖股票的最佳时机III非常类似,搞懂上一题,这一题就不算难题

本题其实和123.买卖股票的最佳时机III很像样,在123题中,我们只能买卖两次,但是本题却能买卖k次!这样我们又应该如何处理呢?

思路

  • 确定dp数组以及下标的含义

仍然使用一个二维dp[i][j]:第i天的状态为j,所剩下的最大现金是dp[i][j]

j的状态表示为:

  • 0 表示不操作
  • 1 第一次买入
  • 2 第一次卖出
  • 3 第二次买入
  • 4 第二次卖出

除了0以外,偶数就是卖出,奇数就是买入

题目要求是至多有K笔交易,那么那么j的范围就定义为 2 * k + 1 就可以了。

vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
  • 确定递推公式

跟之前的三道题都是老样子(一定要做买卖股票的最佳时机I~III这里的三道题)。

达到dp[i][1]状态,有两个具体操作:

  • 操作一:第i天买入股票了,那么dp[i][1] = dp[i - 1][0] - prices[i]
  • 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

选最大的,所以 dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])

同理dp[i][2]也有两个操作:

  • 操作一:第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][1] + prices[i], dp[i - 1][2])

剩下的dp[i][2+1]dp[i][2+2]也就是我们的第二次买入、第二次卖出的情况以此类推,就有

for (int j = 0; j < 2 * k - 1; j += 2) {
    dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
  • dp数组的初始化

我们的每一天状态都依赖于前一天的状态,所以我们只初始化dp[0][j]

再一个,我们要初始化第0天的每一次买入卖出操作,

其实很直观我们每次买入肯定都是-price[0],每一次在同一天买入卖出肯定都剩0元,所以:

for (int j = 1; j < 2 * k; j += 2) {
    dp[0][j] = -prices[0];
}

在初始化的地方同样要类比j为偶数是卖、奇数是买的状态

  • 确定遍历顺序

跟以前一样,从前向后

  • 举例推导dp数组

以输入[1,2,3,4,5],k=2为例。并且最后一天的最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解

CPP代码

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

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

//滚动数组
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

        int len = prices.size();
        vector<int> dp(2 * k + 1, 0);
        
        for (int i = 2; i <= 2 * k; i += 2) {
            dp[i - 1] -= prices[0];
        }

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

        return dp[2 * k];
    }
};

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

力扣题目链接

文章讲解:309.最佳买卖股票时机含冷冻期

视频讲解:动态规划来决定最佳时机,这次有冷冻期!| LeetCode:309.买卖股票的最佳时机含冷冻期

状态:在思考的过程中,要学会分化某些情况的递推公式。本题明显需要把

本题和之前又有什么区别呢?本题加上了一个冷冻期,也就是我们在卖出股票后,第二天不能再买入,必须得隔一天

思路

  1. 确定dp数组以及下标的含义

跟之前一样dp[i][j]表示第i天状态为j,所剩下的最多现金为dp[i][j]

首先可以有一个粗略的状态定义:今天买入、今天卖出、今天是冷冻期

本题的状态有如下四种:

状态一:持有股票(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)

状态二:不持有股票

  • **状态2.1:**不持有股票,但是保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
  • **状态2.2:**今天卖出股票

状态三:今天为冷冻期,但是仅限今天!

综上所述,j的状态为:
0:状态1
1:状态2.1
2:状态2.2
3:状态3

  1. 确定递推公式

现在递推公式其实就直接分状态推理,比较简单:


持有股票状态(状态一)即:dp[i][0],有两个具体操作:

  • 操作一:前一天就是持有股票状态(状态一),dp[i][0] = dp[i - 1][0]
  • 操作二:今天买入了,有两种情况
    • 前一天是冷冻期(状态四),dp[i - 1][3] - prices[i]
    • 前一天是保持卖出股票的状态(状态2.1),dp[i - 1][1] - prices[i]

所以,本状态递推公式: d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 3 ] − p r i c e s [ i ] , d p [ i − 1 ] [ 1 ] − p r i c e s [ i ] ) dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]) dp[i][0]=max(dp[i1][0],dp[i1][3]prices[i],dp[i1][1]prices[i])
需要注意的是max只接受两个传参。


达到保持卖出股票状态二(状态2.1)即:dp[i][1],有两个具体操作:

  • 操作一:前一天就是卖股票状态(状态2.1):dp[i-1][1]
  • 操作二:前一天是冷冻期(状态3):dp[i-1][3]

所以,本状态递推公式:dp[i][1] = max(dp[i - 1][1], dp[i - 1][3])

达到今天就卖出股票状态二(状态2.2),此时昨天一定是持有股票的状态,所以今天才能卖出,所以只有一个操作
dp[i][2] = dp[i - 1][0] + prices[i]


达到冷冻期状态(状态四),即:dp[i][3],这样的话昨天一定把股票卖了,所以只有一个操作:
dp[i][3] = dp[i - 1][2];


综上所述,递推代码如下:

dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
  1. dp数组如何初始化

还是比较直观的, 也就是dp[0][0]=-prices[0]

状态二状态三为了不影响第二天的递推,均初始化为0.

  1. 确定遍历顺序

老样子 ,从前向后

  1. 举例推导dp数组

以 [1,2,3,0,2] 为例,dp数组如下:

2021032317451040

CPP代码

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

714.买卖股票的最佳时机含手续费

力扣题目链接

文章链接:714.买卖股票的最佳时机含手续费

视频链接:动态规划来决定最佳时机,这次含手续费!| LeetCode:714.买卖股票的最佳时机含手续费

手续费来喽!本题中也可以进行无线次买卖,但是每次交易都必须支付手续费,题目中指明买卖一次,只叫一笔手续费,那么我们定义每次买入不交,卖出交手续费。

所以其实本题与动态规划:122.买卖股票的最佳时机II其实是一样一样的。只需要在计算卖出操作的时候减去手续费就可以了

思路

这里只对递推公式的不同点进行讲解,因为其他地方与动态规划:122.买卖股票的最佳时机II一摸一样。

首先明确:dp[i][0] 表示第i天持有股票所省最多现金。dp[i][1] 表示第i天不持有股票所得最多现金

如果我们在第i天持有股票,即dp[i][0],所有只有两种状态,第i-1天已经持有第i天买入,这两种状态我们都不用交手续费

如果在第i天不持有股票,即dp[i][1],也只有两种情况,第i-1天就不持有股票第i天卖出股票,此时我们需要对第二种状态交手续费,也就是直接减去手续费即可dp[i - 1][0] + prices[i] - fee

总体代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] -= prices[0]; // 持股票
        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] - fee);
        }
        return max(dp[n - 1][0], dp[n - 1][1]);
    }
};
  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值