1. 股票买卖时机的核心逻辑与解题框架
股票买卖时机问题是算法面试中的经典题型,也是量化交易的基础模型。这类问题通常给定一段时间内的股票价格序列,要求计算在特定交易限制下能够获得的最大利润。看似简单的买卖决策背后,蕴含着动态规划、贪心算法等核心思想。
在真实交易场景中,投资者面临三个关键约束:交易次数限制(1次、多次或有限次)、交易冷却期(如T+1规则)、以及手续费成本。LeetCode上的买卖股票系列问题(121题、122题、123题)恰好对应了不同约束条件下的最优决策模型。
提示:虽然题目描述的是股票交易,但这类问题的解法可以迁移到任何需要在一系列数值中寻找最优买卖点的场景,比如商品囤货、外汇套利等。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题一:单次交易的最大利润(Best Time to Buy and Sell Stock I)
2.1 问题描述与暴力解法
给定一个数组prices,其中prices[i]表示第i天股票的价格。你只能完成最多一笔交易(即买入和卖出一支股票一次),设计算法计算最大利润。
最直观的解法是双重循环:对于每一天i的价格作为买入点,遍历其后每一天j的价格作为卖出点,计算所有可能的profit = prices[j] - prices[i],取最大值。这种方法时间复杂度为O(n²),在数据量较大时性能堪忧。
python复制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
2.2 优化解法:动态规划思路
更高效的解法是维护一个遍历过程中的最低价min_price,同时计算当前价格与min_price的差值作为潜在利润:
python复制def maxProfit(prices):
min_price = float('inf')
max_profit = 0
for price in prices:
min_price = min(min_price, price)
max_profit = max(max_profit, price - min_price)
return max_profit
这个O(n)时间复杂度的解法之所以有效,是因为最大利润必然发生在最低买入价之后。我们只需要记住已经遍历过的最低价格,就能在后续每天计算出潜在的利润。
注意:当价格序列单调递减时,最大利润为0(即不进行任何交易),这是需要特殊处理的边界情况。
3. 问题二:无限次交易的最大利润(Best Time to Buy and Sell Stock II)
3.1 问题描述与贪心解法
现在允许进行多次交易(买入和卖出一支股票多次),但不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。此时获取最大利润的策略是抓住所有的上升波段。
贪心算法的思路简单直接:只要今天的价格比昨天高,就进行昨天买入今天卖出的操作,累计所有正收益:
python复制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.2 动态规划视角的理解
从状态机的角度考虑,每天结束时我们有两种状态:持有股票或不持有股票。定义dp[i][0]为第i天不持有股票时的最大利润,dp[i][1]为持有股票时的最大利润。状态转移方程为:
code复制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]) # 保持持仓或买入
虽然贪心解法更简洁,但动态规划的框架为后续更复杂的问题(如含手续费、冷却期等情况)提供了可扩展的思路基础。
4. 问题三:最多两次交易的最大利润(Best Time to Buy and Sell Stock III)
4.1 问题描述与状态定义
这是系列中最具挑战性的问题:最多可以完成两笔交易,且不能同时参与多笔交易。此时简单的贪心策略不再适用,需要更精细的状态管理。
我们可以将每天的状态划分为五种:
- 未进行任何操作
- 第一次买入股票
- 第一次卖出股票
- 第二次买入股票
- 第二次卖出股票
定义dp[i][j]表示第i天处于状态j时的最大收益,其中j取0到4。关键的状态转移方程如下:
python复制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]) # 第二次买入
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]) # 第二次卖出
4.2 空间优化实现
由于每天的状态只依赖于前一天的状态,可以将空间复杂度从O(n)优化到O(1):
python复制def maxProfit(prices):
buy1 = buy2 = -float('inf')
sell1 = sell2 = 0
for price in prices:
buy1 = max(buy1, -price)
sell1 = max(sell1, buy1 + price)
buy2 = max(buy2, sell1 - price)
sell2 = max(sell2, buy2 + price)
return sell2
这个解法通过维护四个变量,在遍历过程中不断更新第一次买入/卖出和第二次买入/卖出的最优决策。最终sell2就是最多两次交易的最大利润。
5. 通用解题框架与扩展问题
5.1 交易次数限制的通用解法
对于最多k次交易的问题,可以扩展状态转移方程。定义dp[i][j][l]表示第i天,已经完成j次交易(买入算半次),当前持有(l=1)或不持有(l=0)股票时的最大收益。状态转移需要考虑交易次数的限制:
python复制# 初始化
dp[0][0][0] = 0
dp[0][0][1] = -prices[0]
# 状态转移
for i in range(1, n):
for j in range(k+1):
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j-1][1] + prices[i])
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j][0] - prices[i])
5.2 含交易费用的变种问题
当每笔交易需要支付固定费用fee时,只需在卖出时扣除费用:
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
5.3 冷却期限制的问题
当卖出后需要等待一天才能再次买入时,状态转移需要额外考虑冷却期:
python复制def maxProfit(prices):
n = len(prices)
dp = [[0]*3 for _ in range(n)]
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][2]) # 冷却或保持空仓
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) # 保持持仓或买入
dp[i][2] = dp[i-1][1] + prices[i] # 卖出进入冷却
return max(dp[-1][0], dp[-1][2])
在实际编码面试中,理解这些变种问题的内在联系比死记硬背解法更重要。建议从最简单的单次交易问题入手,逐步增加约束条件,观察状态转移方程的变化规律。
