123. 买卖股票的最佳时机III
本题比之前两题难很多,之前的题目确定了“买卖一次”或者“无限买卖”,而本题规定的是“最多买卖两次”,也就是说可以买卖一次、买卖两次、完全不进行买卖,显得非常复杂。然而,解题思路依然是最古老、最有效的分类讨论:先确定每一天有什么样的不同状态,然后对于每种状态进行递推。
比较明显的,每一天最多有五种状态:
- 还没经过股票的买卖
- 虽然这个状态是存在并且合理的,但是我们可以确定,这个状态下的净利润必然是 0,其实也就没有递推的意义
- 第一次持有股票
- 第一次未持有股票(买入过股票之后)
- 第二次持有股票
- 第二次未持有股票(买入过股票之后)
- dp 数组的下标含义:
dp[i][0]:第 i 天结束第一次持有股票的最大净利润dp[i][1]:第 i 天结束第一次未持有股票的最大净利润dp[i][2]:第 i 天结束第二次持有股票的最大净利润dp[i][3]:第 i 天结束第二次未持有股票的最大净利润
- dp 递推公式:
dp[i][0] = max(dp[i-1][0], -prices[i]):如果第 i 天结束时是第一次持有股票,那么第 i-1 天可能持有股票,也可能没有经历过股票的买卖操作;dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]):如果第 i 天结束时是第一次未持有股票,那么第 i-1 天可能是第一次未持有股票,也可能是第一次持有股票后在第 i 天卖出;dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i]):如果第 i 天结束时是第二次持有股票,那么第 i-1 天可能是第二次持有股票,也可能是第一次未持有股票后在第 i 天买入;dp[i][3] = max(dp[i-1][3], dp[i-1][2] + prices[i]):如果第 i 天结束时是第二次未持有股票,那么第 i-1 天可能是第二次未持有股票,也可能是第二次持有股票后在第 i 天卖出。
- dp 数组的初始化:在第一天的时候,如果是未持有股票的状态,那么净利润就是 0;如果是持有股票的状态,净利润就是
-prices[0]- 题目没有明确否是可以在同一天内同时买卖股票,但由于题目允许长度为一的
prices,很显然同时买卖是允许的。所以第一天的第一次未持有股票可以看作是“买了再卖”,第一天的第二次未持有股票可以看作是“买了再卖,再买再卖”
- 题目没有明确否是可以在同一天内同时买卖股票,但由于题目允许长度为一的
- dp 的遍历顺序:从前向后遍历
- 举例推导:
prices = [3,3,5,0,0,3,1,4]
| 3 | 3 | 5 | 0 | 0 | 3 | 1 | 4 | |
|---|---|---|---|---|---|---|---|---|
| 0 | -3 | -3 | -3 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 2 | 2 | 2 | 3 | 3 | 4 |
| 2 | -3 | -3 | -3 | 2 | 2 | 2 | 2 | 2 |
| 3 | 0 | 0 | 2 | 2 | 2 | 5 | 5 | 6 |
本题的返回值依然很有趣,最自然的写法是在 dp[-1] 的四个值中取最大值。在之前的题中,已经解释过,想要受益最大,最后一天必然需要将股票卖出。然而根据定义,dp[-1][1] <= dp[-1][3],因为如果进行一次买卖就能获得最大净利润,那么总可以在最后一天进行一次(无意义)的买入+卖出。所以最终直接返回 dp[-1][3] 即可。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# dp[0] represents carrying the stock for the first time
# dp[1] represents carrying no stock for the first time
# dp[2] represents carrying the stock for the second time
# dp[3] represents carrying no stock for the second time
dp = [[0] * 4 for _ in range(len(prices))]
dp[0] = [-prices[0], 0, -prices[0], 0]
for i in range(1, len(prices)):
dp[i][0] = max(dp[i-1][0], -prices[i])
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])
return dp[-1][3]
188. 买卖股票的最佳时机IV
如果第一次做股票问题看到这道题,堪称是王炸。但是按照顺序解题的话,很明显这道题是上一题的一个变种,无非是从
n
×
4
n\times4
n×4 的 dp 数组变成了
n
×
2
k
n\times 2k
n×2k 的 dp 数组。为了解题时的状态清晰,我选择用
n
×
k
×
2
n\times k \times 2
n×k×2 的三维数组,但本质上是一样的,无非就是在内部遍历时多加了一个遍历第 j 次(未)持有股票的遍历。
为了省略“没有进行过股票买卖”的讨论,要额外讨论一下 j=0 的情况。
本题总让我觉得跟多重背包有点类似,就是在原题的基础上进行了升维处理,但是题目的含义没有改变。
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
# dp[i][j][0] represents the max profit without holding the stock on day ifor the j-th time
# dp[i][j][1] represents the max profit while holding the stock on day i for the j-th time
dp = [[[0, 0] for j in range(k)] for _ in range(len(prices))]
for j in range(k):
dp[0][j] = [0, -prices[0]]
# dp formula
for i in range(1, len(prices)):
for j in range(k):
if j == 0:
dp[i][j][1] = max(dp[i-1][j][1], -prices[i])
else:
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
return dp[-1][-1][0]
文章讲述了如何运用动态规划解决股票买卖问题,特别关注在最多两次买卖限制下的最大净利润计算。通过分类讨论不同状态并设计dp数组,逐步推导出递推公式,最后给出了两个版本的解决方案,分别对应单次交易限制和多次交易次数作为参数的情况。
2005

被折叠的 条评论
为什么被折叠?



