1. 动态规划在股票交易问题中的实战应用
作为一名经历过多次算法面试的老兵,我深知股票买卖系列问题在技术面试中的高频出现率。今天我想分享力扣上三道经典的股票交易问题:188.买卖股票的最佳时机IV、309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费。这三个问题层层递进,覆盖了动态规划在金融交易场景中的典型应用,也是检验算法工程师基本功的试金石。
这三道题的核心都是基于状态机的动态规划解法,但各自有不同的约束条件:
- 188题限制了交易次数(k次)
- 309题引入了交易冷冻期
- 714题则考虑了交易手续费
理解这些变种问题的共性和差异,能帮助我们在面对真实金融系统的交易策略开发时,快速建立数学模型。下面我将从状态定义、转移方程和边界条件三个维度,详细拆解每个问题的解法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 188.买卖股票的最佳时机IV(限定交易次数)
2.1 问题重述与分析
给定一个整数数组prices表示股票每日价格,最多完成k笔交易(买+卖算一次完整交易),求能获得的最大利润。这是121题(交易一次)和122题(无限次交易)的进阶版本。
关键约束:
- 不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)
- 交易次数上限k可能很大(需要优化空间复杂度)
2.2 动态规划状态设计
我们定义两个状态数组:
- hold[i][j]:第i天结束时,已经完成j次交易且持有股票时的最大利润
- cash[i][j]:第i天结束时,已经完成j次交易且不持有股票时的最大利润
状态转移方程:
code复制hold[i][j] = max(hold[i-1][j], cash[i-1][j] - prices[i])
cash[i][j] = max(cash[i-1][j], hold[i-1][j-1] + prices[i])
初始化条件:
- hold[0][0] = -prices[0]
- cash[0][0] = 0
- 其他hold[0][j]和cash[0][j]初始为-∞(不可能状态)
2.3 空间优化实现
由于每天的状态只依赖前一天,可以优化到O(k)空间:
python复制def maxProfit(k, prices):
if not prices: return 0
n = len(prices)
if k >= n//2: # 退化为无限次交易
return sum(max(0, prices[i]-prices[i-1]) for i in range(1,n))
hold = [-float('inf')]*(k+1)
cash = [0]*(k+1)
hold[0] = -prices[0]
for i in range(1, n):
for j in range(k, 0, -1): # 倒序避免覆盖
cash[j] = max(cash[j], hold[j] + prices[i])
hold[j] = max(hold[j], cash[j-1] - prices[i])
hold[0] = max(hold[0], -prices[i]) # 第0次交易
return max(cash)
关键技巧:当k≥n/2时,问题退化为无限次交易(122题),可以直接贪心求解,避免不必要的计算。
3. 309.最佳买卖股票时机含冷冻期
3.1 冷冻期约束的影响
在卖出股票后需要等待一天才能再次买入,这个约束改变了状态转移的逻辑。我们需要区分"刚卖出"和"可以买入"两种不持有股票的状态。
3.2 三状态动态规划
定义三个状态:
- hold:持有股票
- cooldown:刚卖出股票,处于冷冻期
- ready:可以买入股票
状态转移关系:
code复制hold[i] = max(hold[i-1], ready[i-1] - prices[i])
cooldown[i] = hold[i-1] + prices[i]
ready[i] = max(ready[i-1], cooldown[i-1])
最终结果是max(cooldown[n-1], ready[n-1])
3.3 实现代码
python复制def maxProfit(prices):
if not prices: return 0
hold = -prices[0]
cooldown = -float('inf')
ready = 0
for i in range(1, len(prices)):
new_hold = max(hold, ready - prices[i])
new_cooldown = hold + prices[i]
new_ready = max(ready, cooldown)
hold, cooldown, ready = new_hold, new_cooldown, new_ready
return max(cooldown, ready)
注意事项:冷冻期只影响买入操作,不影响卖出。有些解法会错误地将冷冻期同时应用于买卖双方。
4. 714.买卖股票的最佳时机含手续费
4.1 手续费的处理方式
每次卖出时需要支付固定手续费fee,这会影响我们的卖出决策。手续费可以理解为卖出价格的减少。
4.2 两种状态解法
与122题(无限次交易)类似,但需要在卖出时扣除手续费:
python复制def maxProfit(prices, fee):
hold = -prices[0]
cash = 0
for i in range(1, len(prices)):
cash = max(cash, hold + prices[i] - fee)
hold = max(hold, cash - prices[i])
return cash
4.3 贪心算法的替代方案
对于这个问题,还可以使用贪心算法:
python复制def maxProfit(prices, fee):
min_price = prices[0]
profit = 0
for price in prices[1:]:
if price < min_price:
min_price = price
elif price > min_price + fee:
profit += price - min_price - fee
min_price = price - fee # 关键步骤
return profit
操作心得:贪心解法中的
min_price = price - fee是为了避免重复扣除手续费,这是容易被忽略的细节。
5. 问题对比与实战技巧
5.1 状态设计模式总结
| 问题类型 | 状态维度 | 关键约束处理 |
|---|---|---|
| 基本问题 | hold/cash | 无 |
| 限制次数 | 增加交易次数维度 | 倒序更新状态 |
| 冷冻期 | 增加cooldown状态 | 卖出后跳过一天 |
| 手续费 | hold/cash | 卖出时扣除费用 |
5.2 常见错误排查
-
边界条件错误:
- 忘记初始化hold[0] = -prices[0]
- 冷冻期问题中错误初始化cooldown状态
-
状态转移遗漏:
- 在限制交易次数问题中,忘记处理k≥n/2的特殊情况
- 冷冻期问题中混淆ready和cooldown的转移逻辑
-
空间优化陷阱:
- 限制次数问题中正序更新导致状态覆盖
- 使用贪心算法时忘记调整min_price
5.3 性能优化建议
-
对于188题,当k≥n/2时直接退化为贪心算法,时间复杂度从O(nk)降到O(n)
-
所有问题都可以优化到O(1)空间复杂度,只需维护几个状态变量而非整个数组
-
在实际面试中,建议先写出完整DP表格,再说明空间优化思路,展现完整的思考过程
6. 扩展应用与实际场景
这三类问题虽然以股票交易为背景,但其动态规划思想可以应用到许多实际场景:
-
资源调度问题:比如服务器资源分配,其中"冷冻期"对应资源释放后的冷却时间
-
库存管理:交易次数限制类似于采购批次限制,手续费对应运输成本
-
量化交易策略:这些算法是构建自动化交易系统的基础组件
在真实的量化交易系统中,我们通常会将这些基础模型与以下要素结合:
- 技术指标(均线、MACD等)作为决策信号
- 风险控制模块限制最大回撤
- 交易成本模型(手续费、滑点等)
我建议在掌握这些基础问题后,可以尝试用历史股票数据回测不同的交易策略,观察不同参数(如交易次数、手续费率)对最终收益的影响。这种实践能加深对算法在实际中应用的理解。
