1. 动态规划在股票买卖问题中的应用场景
股票买卖系列问题一直是算法面试中的高频考点,也是动态规划应用的经典场景。这类问题通常会给交易加上各种限制条件,考察我们对状态转移的理解和建模能力。今天我们要讨论的三个题目代表了三种典型的约束条件:交易次数限制(188题)、冷冻期限制(309题)和交易手续费(714题)。
在实际量化交易系统中,这些约束条件都有现实对应。比如高频交易会受到每日交易次数的限制,大资金操作需要考虑市场冲击成本(类似手续费),而监管规则可能要求某些操作后必须等待特定时间(类似冷冻期)。理解这些问题的解法,对开发真实的交易策略很有帮助。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 188.买卖股票的最佳时机IV - 交易次数限制
2.1 问题重述与分析
题目给定一个整数数组prices表示股票每日价格,最多可以完成k笔交易(买入和卖出算一次完整交易),求能获得的最大利润。这是"买卖股票的最佳时机"系列中最通用的形式,当k=1时退化为简单情况,k=2时是另一道经典题目。
这类问题的核心在于定义合适的状态和状态转移方程。我们需要考虑的因素包括:
- 当前是第几天(i)
- 已经进行了多少次交易(j)
- 当前是否持有股票(0未持有,1持有)
2.2 三维DP解法详解
定义dp[i][j][0/1]表示第i天结束时,已经完成j次交易,当前不持有/持有股票的最大利润。状态转移方程如下:
python复制# 初始化
dp = [[[0] * 2 for _ in range(k+1)] for __ in range(len(prices))]
for j in range(k+1):
dp[0][j][1] = -prices[0] # 第一天买入
# 状态转移
for i in range(1, len(prices)):
for j in range(1, k+1):
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
关键点:买入操作会消耗一次交易机会,所以从j-1转移过来
2.3 空间优化技巧
观察状态转移方程可以发现,当前状态只依赖前一天的状态,因此可以将空间复杂度从O(nk)优化到O(k):
python复制dp = [[0] * 2 for _ in range(k+1)]
for j in range(k+1):
dp[j][1] = -prices[0]
for i in range(1, len(prices)):
for j in range(k, 0, -1): # 需要倒序更新
dp[j][0] = max(dp[j][0], dp[j][1] + prices[i])
dp[j][1] = max(dp[j][1], dp[j-1][0] - prices[i])
2.4 边界条件处理
当k >= n//2时,相当于可以无限次交易(因为最多只能进行n//2次完整交易)。这时可以直接用贪心算法计算所有上涨区间的和:
python复制if k >= len(prices) // 2:
profit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
profit += prices[i] - prices[i-1]
return profit
3. 309.最佳买卖股票时机含冷冻期
3.1 冷冻期约束的特点
冷冻期意味着卖出股票后,第二天不能立即买入。这改变了传统的状态转移关系,需要额外考虑这种限制。
3.2 状态定义与转移
我们可以定义三种状态:
- hold:持有股票
- notHold_notCool:不持有股票且不在冷冻期
- notHold_cool:不持有股票且在冷冻期(即前一天刚卖出)
状态转移方程:
python复制hold = max(prev_hold, prev_notHold_notCool - prices[i])
notHold_notCool = max(prev_notHold_notCool, prev_notHold_cool)
notHold_cool = prev_hold + prices[i]
3.3 实现代码示例
python复制def maxProfit(prices):
if not prices: return 0
hold = -prices[0]
notHold_notCool = 0
notHold_cool = 0
for i in range(1, len(prices)):
new_hold = max(hold, notHold_notCool - prices[i])
new_notHold_notCool = max(notHold_notCool, notHold_cool)
new_notHold_cool = hold + prices[i]
hold, notHold_notCool, notHold_cool = new_hold, new_notHold_notCool, new_notHold_cool
return max(notHold_notCool, notHold_cool)
3.4 实际交易中的应用
冷冻期在真实交易中对应着各种交易规则限制。比如某些市场规定卖出后资金需要T+1日才能到账,或者大额交易后需要等待一定时间才能反向操作。理解这种状态转移对开发合规的交易系统很重要。
4. 714.买卖股票的最佳时机含手续费
4.1 手续费的影响分析
每次卖出时需要支付固定手续费,这会影响我们的卖出决策。与无限次交易的问题相比,我们需要更谨慎地决定何时卖出。
4.2 两种状态DP解法
定义两个状态:
- cash:不持有股票时的最大利润
- hold:持有股票时的最大利润
状态转移:
python复制cash = max(prev_cash, prev_hold + prices[i] - fee)
hold = max(prev_hold, prev_cash - prices[i])
4.3 实现与优化
python复制def maxProfit(prices, fee):
cash, hold = 0, -prices[0]
for i in range(1, len(prices)):
cash = max(cash, hold + prices[i] - fee)
hold = max(hold, cash - prices[i])
return cash
注意:手续费只在卖出时扣除,这是常见的交易规则。有些问题可能会在买入时收取手续费,需要相应调整状态转移方程。
4.4 交易成本的实际考量
在实际交易中,手续费只是交易成本的一部分,还可能包括滑点、市场冲击成本等。更精确的模型会将这些因素都考虑进去。这里的解法为更复杂的成本模型提供了基础框架。
5. 股票买卖问题的通用解题框架
5.1 状态定义方法论
通过以上三个问题,我们可以总结出解决股票买卖问题的通用方法:
- 识别问题中的约束条件(交易次数、冷冻期、手续费等)
- 定义足够的状态表示这些约束
- 确定状态之间的转移关系
- 处理边界条件和初始化
5.2 常见变体与对应解法
| 问题变体 | 关键约束 | 状态设计要点 | 空间复杂度 |
|---|---|---|---|
| 无限制交易 | 无 | 只需持有/未持有状态 | O(1) |
| 交易次数限制 | 最多k次交易 | 增加交易次数维度 | O(k) |
| 冷冻期 | 卖出后需等待 | 增加冷冻期状态 | O(1) |
| 手续费 | 每次交易固定成本 | 在卖出时扣除费用 | O(1) |
5.3 调试与验证技巧
在实现这类DP问题时,建议:
- 打印DP表观察状态变化
- 从小测试用例开始(如3天的价格序列)
- 特别注意边界条件(空输入、单日价格等)
- 对比贪心算法的结果(当适用时)
6. 从算法到实际交易的思考
6.1 模型假设与现实差距
这些算法问题做了很多简化假设:
- 可以立即以当前价格买卖任意数量
- 不考虑交易对市场价格的影响
- 手续费是固定值而非比例
在实际交易系统中,我们需要考虑更多复杂因素。
6.2 扩展方向
基于这些基础模型,可以进一步研究:
- 加入仓位管理(每次买卖固定数量)
- 考虑部分成交的情况
- 引入更复杂的交易成本模型
- 结合技术指标作为买卖信号
6.3 性能优化实践
当处理高频数据或长时间序列时:
- 使用滚动数组优化空间
- 并行计算不同时间段
- 预处理价格序列中的关键点
- 使用近似算法处理超大规模数据
我在实际开发量化策略时发现,理解这些基础问题的解法非常重要,但也要清楚它们的局限性。真正的交易系统需要在这些算法基础上,加入风险管理、仓位控制等更多模块。建议先从这些经典问题入手,掌握状态设计和转移的核心思想,再逐步扩展到更复杂的场景。
