提示:DDU,供自己复习使用。欢迎大家前来讨论~
文章目录
- 买卖股票的最佳时机相关题目
- 题目一:121. 买卖股票的最佳时机
- 解题思路:
- 题目二:122.买卖股票的最佳时机II
- 解题思路:
- 题目三: 123.买卖股票的最佳时机III
- 解题思路
- 总结
[[121. 买卖股票的最佳时机](https://leetcode.cn/problems/combinations/)
dp
来存储每一天的两种状态(持有股票和不持有股票)。dp
数组。dp[n-1][1]
,表示在整个时间段内不持有股票时的最大利润。状态解释:
dp[i][0]
):有两种情况,一是之前从未购买股票,在第 i
天购买;二是之前已经购买并持有到第 i
天。因此,dp[i][0]
可以是从前一天持有状态转移来的(dp[i-1][0]
),或者是在第 i
天新购买股票(-prices[i]
,表示花费)。dp[i][1]
):也有两种情况,一是之前从未购买过股票;二是在第 i
天或之前某天卖出股票。因此,dp[i][1]
可以是从前一天不持有状态转移来的(dp[i-1][1]
),或者是在第 i
天卖出股票(dp[i-1][0] + prices[i]
,表示之前持有的利润加上今天卖出的利润)。动态规划五部曲分析:
确定 dp 数组及其含义:
dp[i][0]
表示第 i
天手里持有股票的最大利润。dp[i][1]
表示第 i
天手里不持有股票的最大利润。初始化:
dp[0][0]
初始化为 -prices[0]
,表示第0天买了股票后的利润(负数)。dp[0][1]
初始化为 0
,表示第0天手里不持有股票的利润。递推公式:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
:第 i
天持有股票的状态取决于前一天是否持有股票(dp[i-1][0]
),或者前一天卖掉股票后买入(dp[i-1][1] - prices[i]
)。dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
:第 i
天不持有股票的状态取决于前一天是否持有股票并今天卖掉(dp[i-1][0] + prices[i]
),或者前一天就不持有股票(dp[i-1][1]
)。遍历顺序:
dp[i][0]
和 dp[i][1]
。最终结果:
dp[n][1]
表示在整个时间段内不持有股票时的最大利润,这是我们要求的答案。个人理解:
dp[i][0]
维护了到第 i
天为止,持有股票时的最大利润。dp[i][1]
维护了到第 i
天为止,不持有股票时的最大利润。最终思路:
dp[i][0]
和 dp[i][1]
。dp[n][1]
,表示在整个时间段内不持有股票时的最大利润。以上分析完毕,C++代码如下:
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if (len == 0) return 0;
vector<vector<int>> dp(len, vector<int>(2));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
}
return dp[len - 1][1];
}
};
从递推公式可以看出,dp[i]只是依赖于dp[i - 1]的状态。
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,代码如下:(不熟练原理,可以不需要掌握这个)
// 版本二
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
122. 买卖股票的最佳时机 II
本题和上题的唯一区别是本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)
在动规五部曲中,这个区别主要是体现在递推公式上,其他都和121. 买卖股票的最佳时机 (opens new window)一样的。
重点讲一讲递推公式。
这里重申一下dp数组的含义:
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
注意这里和121. 买卖股票的最佳时机唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况。
在121. 买卖股票的最佳时机中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。
而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。
那么第i天持有股票即dp[i][0],如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]。
再来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[len - 1][1];
}
};
唯一的区别在:
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i-1][0]
),还要考虑之前卖出股票后累积的利润(dp[i-1][1]
)。因此,买入股票的操作可以表示为 dp[i-1][1] - prices[i]
,这反映了在第 i
天买入股票后持有的状态。dp[i][0]
(第 i
天持有股票的最大利润)时,应该考虑从卖出状态转入持有状态的可能性。123. 买卖股票的最佳时机 III
dp[i][j]
表示在第 i
天,处于状态 j
时的最大现金。状态 j
可以是五种之一:无操作、第一次持有股票、第一次卖出股票、第二次持有股票、第二次卖出股票。dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1])
:表示在第 i
天买入股票,可以是今天买入,也可以是延续前一天的买入状态。dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
:表示在第 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])
:第二次卖出股票。dp[0][0] = 0
:第0天无操作。dp[0][1] = -prices[0]
:第0天买入股票。dp[0][2] = 0
:第0天卖出股票(当天买入当天卖出)。dp[0][3] = -prices[0]
:第0天第二次买入股票(假设先前已经卖出)。dp[0][4] = 0
:第0天第二次卖出股票。[1,2,3,4,5]
为例,逐步填充 dp
数组,考虑每一天可能的买卖操作,并更新状态。可以看到红色框为最后两次卖出的状态。
现在最大的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出。如果想不明白的录友也可以这么理解:如果第一次卖出已经是最大值了,那么我们可以在当天立刻买入再立刻卖出。所以dp[4][4]已经包含了dp[4][2]的情况。也就是说第二次卖出手里所剩的钱一定是最多的。
所以最终最大利润是dp[4][4]
以上五部都分析完了,不难写出如下代码:
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = dp[i - 1][0];
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[prices.size() - 1][4];
}
};
买卖股票的最佳时机问题是一个经典的动态规划问题,旨在确定在给定的股票价格列表中买卖股票的最佳时机以最大化利润。
dp[i][0]
表示第 i
天不持有股票的最大利润。dp[i][1]
表示第 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])
:持有股票的状态可以是前一天就持有的延续,或者是在今天买入了股票。dp[0][0]
为 0,表示在第0天不持有股票的利润为0。dp[0][1]
为 -prices[0]
,表示在第0天如果买入股票,则利润为负数(因为你花了钱买股票)。dp[i][0]
和 dp[i][1]
。买卖股票的最佳时机问题通过动态规划方法,考虑每天是否持有股票的两种状态,并通过状态转移方程来更新每天的最大利润。最终,不持有股票的最大利润 dp[n][0]
就是整个时间段内可以获得的最大利润。