Day44 | 动态规划:状态机DP 买卖股票的最佳时机IV && 买卖股票的最佳时机III
在动态规划(DP)问题中,“买卖股票的最佳时机”类问题经常出现在面试和算法竞赛中。今天我们将重点讲解两个经典的股票买卖问题:《买卖股票的最佳时机IV》和《买卖股票的最佳时机III》。这两个问题都可以通过动态规划和状态机方法求解。
一、问题描述与分析
1.1 买卖股票的最佳时机III
问题描述:
给定一个数组 prices
,其中 prices[i]
是第 i
天的股票价格。你最多可以进行 两次 交易,且每次交易必须先买入再卖出,求最大利润。
输入:
- 整数数组
prices
,长度为n
,表示股票每天的价格。
输出:
- 返回可以获得的最大利润。
示例:
textCopy Code输入: prices = [3,2,6,5,0,3] 输出: 7 解释: 在第 2 天买入, 第 3 天卖出,利润为 6 - 2 = 4。 然后在第 5 天买入, 第 6 天卖出,利润为 3 - 0 = 3。 总利润 = 4 + 3 = 7。
思路:
这个问题的核心是如何管理股票的买入与卖出次数。由于最多允许两次交易,我们可以使用动态规划来解决。
1.2 买卖股票的最佳时机IV
问题描述:
给定一个数组 prices
,其中 prices[i]
是第 i
天的股票价格。你最多可以进行 k 次交易,且每次交易必须先买入再卖出,求最大利润。
输入:
- 整数
k
:最多允许的交易次数。 - 整数数组
prices
,长度为n
,表示股票每天的价格。
输出:
- 返回可以获得的最大利润。
示例:
textCopy Code输入: k = 2, prices = [2,4,1] 输出: 2 解释: 在第 1 天买入, 第 2 天卖出,利润为 4 - 2 = 2。
思路:
这个问题的核心是限制交易次数的情况下如何求解最大利润。类似于问题 III,但这里的交易次数是动态的。
二、解题思路:状态机 DP
在这两个问题中,我们的目标是使用动态规划(DP)来求解股票买卖的最大利润。在这类问题中,状态机模型是一个很好的框架。
2.1 状态定义与转换
买卖股票的最佳时机III
我们定义一个 dp
数组,其中 dp[i][j]
表示在第 i
天,进行了最多 j
次交易时的最大利润。
i
代表天数。j
代表交易次数,最大为 2。
状态转移方程可以分为以下几种情况:
- 不进行交易:
dp[i][j] = dp[i-1][j]
。 - 进行买入交易:
dp[i][j] = max(dp[i][j], dp[i-1][j-1] - prices[i])
。 - 进行卖出交易:
dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i])
。
买卖股票的最佳时机IV
在此问题中,最多进行 k
次交易。我们仍然使用 dp[i][j]
表示在第 i
天,进行了最多 j
次交易时的最大利润。
i
代表天数。j
代表交易次数,最大为k
。
状态转移方程与问题 III 类似,只是将交易次数扩展到 k
。
2.2 具体实现
买卖股票的最佳时机III(最多两次交易)
我们首先来实现买卖股票的最佳时机 III。
pythonCopy Codedef maxProfit(prices):
if not prices:
return 0
n = len(prices)
# dp[i][j] 表示第 i 天,最多 j 次交易时的最大利润
dp = [[0] * 3 for _ in range(n)] # 2 次交易,所以 j = 1, 2
for j in range(1, 3): # 最大两次交易
max_diff = -prices[0] # max_diff 表示买入的最大利润
for i in range(1, n):
dp[i][j] = max(dp[i-1][j], prices[i] + max_diff)
max_diff = max(max_diff, dp[i][j-1] - prices[i])
return dp[n-1][2]
# 示例
prices = [3, 2, 6, 5, 0, 3]
print(maxProfit(prices)) # 输出: 7
买卖股票的最佳时机IV(最多k次交易)
接下来,我们实现买卖股票的最佳时机 IV。
pythonCopy Codedef maxProfit(k, prices):
if not prices:
return 0
n = len(prices)
if k >= n // 2:
# 如果交易次数 k 大于等于股票天数的一半,那么可以任意买卖,直接计算所有的利润
return sum(max(prices[i+1] - prices[i], 0) for i in range(n-1))
dp = [[0] * (k+1) for _ in range(n)]
for j in range(1, k+1):
max_diff = -prices[0]
for i in range(1, n):
dp[i][j] = max(dp[i-1][j], prices[i] + max_diff)
max_diff = max(max_diff, dp[i][j-1] - prices[i])
return dp[n-1][k]
# 示例
k = 2
prices = [2, 4, 1]
print(maxProfit(k, prices)) # 输出: 2
2.3 时间复杂度与空间复杂度分析
- 时间复杂度:
O(n*k)
,其中n
为股票的天数,k
为最多的交易次数。在最坏情况下,两个问题的时间复杂度分别为O(n*2)
和O(n*k)
。 - 空间复杂度:
O(n*k)
,用于存储动态规划表格。
三、动态规划的优化
虽然上述方法能够解决问题,但其空间复杂度为 O(n*k)
,可以通过优化空间复杂度来进一步提高性能。通过使用滚动数组或状态压缩技术,我们可以将空间复杂度降低到 O(k)
。
3.1 状态压缩(滚动数组)
对于 DP 题目,如果每次状态只依赖于前一层的状态,那么我们可以通过滚动数组优化空间复杂度。以下是状态压缩的实现:
买卖股票的最佳时机III(优化空间复杂度)
pythonCopy Codedef maxProfit(prices):
if not prices:
return 0
n = len(prices)
dp = [[0] * 3 for _ in range(2)] # 只有 2 层状态
for j in range(1, 3):
max_diff = -prices[0]
for i in range(1, n):
dp[j % 2][i % 2] = max(dp[j % 2][i-1], prices[i] + max_diff)
max_diff = max(max_diff, dp[j % 2][i-1] - prices[i])
return dp[1][2]
# 示例
prices = [3, 2, 6, 5, 0, 3]
print(maxProfit(prices)) # 输出: 7
买卖股票的最佳时机IV(优化空间复杂度)
pythonCopy Codedef maxProfit(k, prices):
if not prices:
return 0
n = len(prices)
if k >= n // 2:
return sum(max(prices[i+1] - prices[i], 0) for i in range(n-1))
dp = [0] * (k+1)
for j in range(1, k+1):
max_diff = -prices[0]
for i in range(1, n):
dp[j] = max(dp[j], prices[i] + max_diff)
max_diff = max(max_diff, dp[j-1] - prices[i])
return dp[k]
# 示例
k = 2
prices = [2, 4, 1]
print(maxProfit(k, prices)) # 输出: 2
3.2 进一步的优化
除了空间压缩,我们还可以在极端情况下做一些优化。例如,当股票价格走势较为平稳,或者我们允许的交易次数 k
比股票的天