代码随想录:动态规划|309.最佳买卖股票时机含冷冻期&714. 买卖股票的最佳时机含手续费

希望通过这篇文章能看到你的收获和感悟,或许你有更好的理解与建议与我沟通交流,
希望能看到你的留言,即使一句话也非常有意义

309. Best Time to Buy and Sell Stock with Cooldown

股票问题的核心:分清楚状态和状态如何转化的。

dp存储状态:持有和不持有的两个状态,细分为4个状态。

  • 持有状态: 0.今天买入或已经买入
  • 不持有状态: 1.今天卖出
    2.冷冻期 (昨日卖出)
    3.过了冷冻期 (早已卖出

而后还可以合并为3个状态,将dp[1]和dp[2]合并为一个状态。 即:不持有 = 没过冷冻期+过了冷冻期。 怎么好理解怎么去想。而且你完全可以把持有状态换成买入状态。

/**
 * @file 309. Best Time to Buy and Sell Stock with Cooldown.cpp
 * @brief Best Time to Buy and Sell Stock with Cooldown
 * can complete many transactions but must sell the stock before buy again.
 * \attention cooldown one day
 * @author Chris
 * @date 2023-12-28
 * @see https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/
 */

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include 
#include 
#include 
#include "../include/doctest.h"

using namespace std;

class Solution {
 public:
  /**
   * @brief 动态规划
   * 状态: 持有状态: 0.今天买入,或已经买入
   *     不持有状态: 1.今天卖出
   *               2.冷冻期 (昨日卖出)
   *               3.过了冷冻期 (早已卖出)
   */
  int maxProfit(vector<int> &prices) {
    int n = prices.size();
    vector<vector<int>> dp(n, vector<int>(4));
    dp[0][0] = -prices[0];
    for (int i = 1; i < n; ++i) {
      //! 0.持有 = 已经持有,继续保持状态 |(昨日冷冻期or过了冷冻期)今天买
      dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][2], dp[i - 1][3]) - prices[i]);
      //! 1.今日卖出 = 昨日持有,今天卖
      dp[i][1] = dp[i - 1][0] + prices[i];
      //! 2.冷冻期 = 昨日卖出
      dp[i][2] = dp[i - 1][1];
      //! 3.过了冷冻期 = 昨天过了冷冻期,继续处于这个状态|昨日冷冻期
      dp[i][3] = max(dp[i - 1][3], dp[i - 1][2]);
    }
    return *max_element(dp.back().begin() + 1, dp.back().end());
  }
};
/// @fn test function
TEST_CASE("Testing function") {
  Solution s;
  SUBCASE("case 1") {
    vector<int> prices1 = {1, 2, 3, 0, 2};
    CHECK(s.maxProfit(prices1) == 3);
  }
  SUBCASE("case 2") {
    vector<int> prices2 = {1};
    CHECK(s.maxProfit(prices2) == 0);
  }
}

vscode编辑器,doxygen document插件自动生成的doxygen注释语法
使用的doctest.h单元测试库
clangd语言服务器
代码风格clang-format格式based Google

714. Best Time to Buy and Sell Stock with Transaction Fee

这一题很简单,就加了个一个手续费。

dp[i]含义 第i天手里最大现金

两个状态:

  • dp[i][0]持有状态
  • dp[i][1]不持有股票状态

递推公式: 卖的时候交手续费

  • 持有: 1。保持持有状态 2。昨天不持有,今天买入
    dp[i][0] = max(dp[i-1][0], dp[i][1] - prices[i])
  • 不持有: 1。保持不持有状态 2。昨天持有,今天卖出。
    dp[i][1] = max(dp[i-1][1], dp[i][0] + prices[i] - fee)

初始化:

第一天买入 dp[0][0] = -prices[0]
dp[0][1] = 0;

递推顺序:第二天开始从前往后

/**
 * @file 714. Best Time to Buy and Sell Stock with Transaction Fee.cpp
 * @brief 714. Best Time to Buy and Sell Stock with Transaction Fee
 * \attention have transaction fee
 * @author Chris
 * @date 2023-12-28
 * @see https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
 */
#include 
using namespace std;
class Solution {
 public:
  int maxProfit(vector<int>& prices, int fee) {
    vector<vector<int>> dp(prices.size(), vector<int>(2));
    // dp[i][0] 持有股票, dp[i][1] 不持有股票
    dp[0][0] = -prices[0];
    for (int i = 1; i < prices.size(); ++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 dp.back().back();
  }
};

贪心算法

无限次交易, 一次大额交易可以转化成小额度连续交易,且当天卖了可以当天再买。

比如[1, 2, 3, 4], 第一天买入第四天卖出,相当于连续操作:第一天买-第二天卖;第二天买-第三天卖;第三天买-第四天买。 (2-1) + (3-2) + (4-3) - fee

2个操作,
    1.买入 遇到最低点记录,买入点
    2.卖出 价格大于(最低价格+手续费)就可卖出, 连续交易,有差价就交易

如果价格单调递增,会进行连续交易,那么minPrice存储的就是当前prices[i]价格,

当记录价格时减去手续费 minPrice = prices[i] - fee;

这样可以避免扣除中间多余的手续费。
除了第一次交易,之后的交易为

res += prices[i] - (prices[i-1] - fee) -fee = prices[i]-prices[i-1]

这样就没有手续费啦!一次连续的交易就扣除了一次手续费。

/// @brief 贪心算法
class Solution2 {
 public:
  /** 2个操作:
   *     买入: 遇到最低点记录,买入点
   *     卖出: 价格大于(最低价格+手续费)就可卖出, 连续交易,有差价就赚
   */
  int maxProfit(vector<int>& prices, int fee) {
    vector<vector<int>> dp(prices.size(), vector<int>(2));
    auto greedy = [](vector<int> prices, int fee) {
      int res = 0;
      int minPrice = prices[0];  ///< 记录最低价
      for (int i = 1; i < prices.size(); ++i) {
        /// 记录最低价,这是我们买入是价格
        if (prices[i] < minPrice) minPrice = prices[i];

        /// 只要有的赚我们就卖了再买,进行连续小额交易,==
        /// 一次大额度、高差价交易。 但是我们只要扣除最后一次手续费即可。
        if (prices[i] > minPrice + fee) {
          res += prices[i] - minPrice - fee;
          /// 如果单调递增,进行连续交易,那么minPrice存储的就是当前prices[i]价格
          /// 这可以避免扣除中间多余的手续费
          minPrice = prices[i] - fee;  ///< 记录当前价格
        }
      }
      return res;
    };
    return greedy(prices, fee);
  }
};

综上所述,可以看出来贪心算法就是找到局部最小值和局部最大值,最小值-最大值,累加起来,就是最大收益。 也可以理解为累加单调递增的区间差值

你可能感兴趣的:(数据结构与算法,动态规划,算法,c++,vscode,贪心算法)