题目描述
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
解题思路
“股票买卖”这个系列有好几道题,labuladong大佬有一个“一个通用方法团灭 6 道股票问题”,推荐!相似问题解题方法统一一下,不然容易做每个题都用不同的方法,总结不出套路。(动态规划三把斧)
-
因为本题中规定买入,卖出和冷冻操作,规定上一次卖出和下一次买入之间需要至少一天的冷冻期,因此我们定义三种状态
sell
、buy
和cooldown
,分别对应了到第i
天为止最后一个操作是买入、卖出和冷冻所对应的最大利润(注意并不是第i
天就是那个操作!例如第i-1
天卖出,第i
天无操作,则第i
天为止最后一个操作也是卖出操作sell[i]
),则状态转移方程如下:(此题对状态表示的理解很重要!)sell[i] = max(sell[i-1], buy[i-1] + prices[i]);
buy[i] = max(buy[i-1], cooldown[i-1] - prices[i]);
cooldown[i] = max(cooldown[i-1], sell[i-1]);
注:如何理解第
i-1
天是sell
状态,第i
天还是sell
呢?其实是这样的,
sell[i]
代表的是到第i
天为止,最后一个操作是卖出操作,即昨天卖出今天无操作或者昨天买入今天卖出两种状态,所以sell[i] = max(sell[i-1], buy[i-1] + prices[i]);
。即我们可以把第
i
天分为确实有操作(第i
天进行了买入/卖出/冷冻操作)和无操作两种状态。
参考代码
class Solution {
public:
int maxProfit(vector<int>& prices) {
int length = prices.size();
if(length == 0)
return 0;
int buy[length], sell[length], cooldown[length];
memset(buy, 0, sizeof(buy)); // 用vector<int> buy(length,0) 更方便
memset(sell, 0, sizeof(sell));
memset(cooldown, 0, sizeof(cooldown));
buy[0] = -prices[0]; // 初始化,第一天只能执行买入操作
for(int i = 1; i < length; i++){
buy[i] = max(buy[i-1], cooldown[i-1] - prices[i]);
sell[i] = max(sell[i-1], buy[i-1] + prices[i]);
cooldown[i] = max(cooldown[i-1], sell[i-1]);
}
return sell[length-1]; // 最后一定是返回这个
}
};
一个通用方法团灭 6 道股票问题
// 每次 sell 之后要等一天才能继续交易。只要把这个特点融入上一题(122、买卖股票的最佳时机 II )的状态转移方程即可:
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
// dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
// 解释:第 i 天选择 buy 的时候,要从第 i-2 天的状态转移,而不是 i-1 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int length = prices.size();
if(length <= 1)
return 0;
int dp_i_0 = 0, dp_i_1 = -prices[0];
int dp_pre_0 = 0;
for (int i = 1; i < length; i++) {
int temp = dp_i_0;
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, dp_pre_0 - prices[i]);
dp_pre_0 = temp;
}
return dp_i_0;
}
};