1. 问题背景与核心需求
股票买卖时机选择是量化交易和算法设计中的经典问题。给定一个长度为n的价格数组prices,其中prices[i]表示第i天的股票价格,我们需要设计算法计算能够获得的最大收益。这个问题看似简单,但蕴含着动态规划和贪心算法这两种重要算法思想的精妙应用。
在实际交易场景中,这个算法可以用于:
- 单次交易的最大收益计算(对应BM80问题)
- 多次交易的最大收益计算(对应BM81问题)
- 作为更复杂交易策略的基础组件
2. 单次交易最优解(BM80问题)
2.1 暴力解法分析
最直观的解法是双重循环遍历所有可能的买卖组合:
def maxProfit(prices): max_profit = 0 for i in range(len(prices)): for j in range(i+1, len(prices)): profit = prices[j] - prices[i] if profit > max_profit: max_profit = profit return max_profit这种方法时间复杂度O(n²),在数据量大时性能较差。
2.2 动态规划解法
更优的解法是使用动态规划,将问题分解为子问题:
- 定义状态:dp[i]表示前i天的最低价格
- 状态转移方程: dp[i] = min(dp[i-1], prices[i])
- 最大利润: max_profit = max(max_profit, prices[i] - dp[i])
实现代码:
def maxProfit(prices): if not prices: return 0 min_price = prices[0] max_profit = 0 for price in prices[1:]: max_profit = max(max_profit, price - min_price) min_price = min(min_price, price) return max_profit这种方法将时间复杂度优化到O(n),空间复杂度O(1)。
2.3 贪心算法解法
贪心算法的思路是:
- 维护一个当前最小值
- 每次计算当前价格与最小值的差
- 始终保持追踪最大差值
实现上与动态规划类似,但思考角度不同:
def maxProfit(prices): if len(prices) < 2: return 0 min_price = prices[0] max_profit = 0 for price in prices: if price < min_price: min_price = price elif price - min_price > max_profit: max_profit = price - min_price return max_profit3. 多次交易最优解(BM81问题)
3.1 问题描述
现在允许进行多次交易(买入卖出),但不能同时参与多笔交易(必须在再次买入前卖出之前的股票),求最大收益。
3.2 贪心算法解法
关键观察:所有上升区间的累计和就是最大利润。
实现方案:
def maxProfit(prices): profit = 0 for i in range(1, len(prices)): if prices[i] > prices[i-1]: profit += prices[i] - prices[i-1] return profit这种方法时间复杂度O(n),空间复杂度O(1),是最优解法。
3.3 动态规划解法
定义状态:
- 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])实现代码:
def maxProfit(prices): if not prices: return 0 n = len(prices) dp = [[0]*2 for _ in range(n)] dp[0][0] = 0 dp[0][1] = -prices[0] for i in range(1, n): 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]) return dp[n-1][0]可以进一步优化空间复杂度到O(1)。
4. 算法选择与性能对比
4.1 单次交易场景
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力 | O(n²) | O(1) | 小数据集 |
| DP | O(n) | O(1) | 通用方案 |
| 贪心 | O(n) | O(1) | 最优方案 |
4.2 多次交易场景
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 贪心 | O(n) | O(1) | 最优方案 |
| DP | O(n) | O(n) | 可扩展 |
5. 实际应用与扩展
5.1 交易费用考虑
现实交易中存在手续费,可以修改状态转移方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i] - fee) dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])5.2 冷却期限制
卖出后需要等待一天才能买入:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])5.3 交易次数限制
对于最多k次交易的通用解法,状态需要增加交易次数的维度。
6. 常见问题与调试技巧
6.1 边界条件处理
常见错误包括:
- 空输入数组
- 单元素数组
- 单调递减价格序列
提示:始终先检查输入有效性,添加边界条件处理。
6.2 状态初始化
动态规划中初始状态的设置很关键:
- dp[0][0] = 0 (第一天不持有)
- dp[0][1] = -prices[0] (第一天持有)
6.3 性能优化
对于大规模数据:
- 优先选择贪心算法
- 动态规划可以优化空间复杂度
- 考虑并行计算可能
7. 实战案例演示
使用akshare获取真实股票数据测试:
import akshare as ak # 获取股票历史数据 stock_data = ak.stock_zh_a_daily(symbol="sh600519", adjust="hfq") # 计算最大单次交易收益 prices = stock_data['close'].values print("单次交易最大收益:", maxProfit(prices)) # 计算多次交易最大收益 print("多次交易最大收益:", maxProfit_multiple(prices))8. 算法选择建议
- 单次交易:优先使用贪心/DP的O(n)解法
- 多次无限制交易:贪心算法最优
- 有交易限制:使用动态规划
- 有额外约束(手续费、冷却期):扩展动态规划模型
在实际量化交易系统中,这些算法通常作为基础组件,与风险管理、仓位控制等模块结合使用。