希望通过这篇文章能看到你的收获和感悟,或许你有更好的理解与建议与我沟通交流,
希望能看到你的留言,即使一句话也非常有意义
股票问题的核心:分清楚状态和状态如何转化的。
dp存储状态:持有和不持有的两个状态,细分为4个状态。
而后还可以合并为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
这一题很简单,就加了个一个手续费。
dp[i]含义 第i天手里最大现金
两个状态:
递推公式: 卖的时候交手续费
dp[i][0] = max(dp[i-1][0], dp[i][1] - prices[i])
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);
}
};
综上所述,可以看出来贪心算法就是找到局部最小值和局部最大值,最小值-最大值,累加起来,就是最大收益。 也可以理解为累加单调递增的区间差值