买卖股票的最佳时机II
大约 6 分钟教学文档动态规划
题目描述

题目分析
股票买卖最大利润问题分析
题目理解
首先,我们需要明确题目的关键约束条件:
- 最多只能持有一股股票
- 可以在同一天多次买卖(这意味着可以在同一天先卖出再买入)
- 目标是获得最大利润
贪心策略分析
由于我们可以在同一天多次交易,这意味着我们实际上可以"跳过"任何会导致亏损的持有期。
考虑价格序列:如果明天的价格比今天高,那么今天买入明天卖出就能获利。由于没有交易次数限制,我们可以把所有上涨的区间都抓住。
关键洞察:最大利润等于所有价格上升段的利润之和。
例如,价格序列为 [1, 2, 3, 4]:
- 如果我们从第1天持有到第4天:利润 = 4 - 1 = 3
- 如果我们每天都买卖:(2-1) + (3-2) + (4-3) = 1 + 1 + 1 = 3
两种策略结果相同!这说明我们可以将连续的上涨分解为每天的上涨来处理。
算法思路
遍历价格数组,只要发现明天的价格比今天高,就进行一次买卖(今天买,明天卖),将利润累加。
这样做的正确性基于:
- 所有正收益的交易都应该执行
- 由于可以同天多次交易,我们可以完美捕捉每一个上涨区间
- 下跌的区间我们完全跳过(不持有股票)
时间复杂度
- 时间复杂度:O(n),只需要遍历一次数组
- 空间复杂度:O(1),只使用常数额外空间
Python代码实现
def maxProfit(prices):
"""
计算股票买卖的最大利润
Args:
prices: List[int] - 股票每天的价格
Returns:
int - 最大利润
"""
if not prices or len(prices) <= 1:
return 0
max_profit = 0
# 遍历每一天(除了最后一天)
for i in range(len(prices) - 1):
# 如果明天的价格比今天高,就今天买入明天卖出
if prices[i + 1] > prices[i]:
max_profit += prices[i + 1] - prices[i]
return max_profit
# 测试用例
if __name__ == "__main__":
# 测试用例1
prices1 = [7, 1, 5, 3, 6, 4]
print(f"输入: {prices1}")
print(f"输出: {maxProfit(prices1)}") # 期望输出: 7 (1->5 赚4, 3->6 赚3)
# 测试用例2
prices2 = [1, 2, 3, 4, 5]
print(f"输入: {prices2}")
print(f"输出: {maxProfit(prices2)}") # 期望输出: 4 (每天都有上涨)
# 测试用例3
prices3 = [7, 6, 4, 3, 1]
print(f"输入: {prices3}")
print(f"输出: {maxProfit(prices3)}") # 期望输出: 0 (价格一直下跌)
# 测试用例4
prices4 = [1]
print(f"输入: {prices4}")
print(f"输出: {maxProfit(prices4)}") # 期望输出: 0 (只有一天)
算法正确性证明
这个贪心算法的正确性可以通过以下方式理解:
最优子结构:全局最优解包含局部最优解。每个上涨区间都应该被利用。
贪心选择性质:在每个决策点(每一天),如果明天价格更高,立即买入是局部最优选择,且不会影响后续的最优选择。
无后效性:由于可以同天多次交易,今天的决策不会限制明天的选择。
注意事项与题目启发
需要注意的问题
1. 边界条件处理
# 正确处理空数组和单元素数组
if not prices or len(prices) <= 1:
return 0
- 空数组或只有一个价格时,无法进行任何交易
- 循环范围是
range(len(prices) - 1),避免数组越界
2. 题目约束的理解
- 关键点:题目允许"同一天多次买卖",这是贪心策略成立的前提
- 如果题目改为"不能同天买卖"或"有冷冻期",这个简单贪心就不适用了
- 必须确认最多持有一股的约束是否影响策略(在此题中不影响)
3. 数值溢出问题
- 虽然Python整数不会溢出,但在其他语言中需要注意利润累加可能导致的溢出
- 实际应用中应考虑价格和利润的数据类型范围
4. 算法适用范围
- 这个解法仅适用于无交易次数限制的情况
- 如果有交易次数限制(如最多k次交易),需要使用动态规划
- 如果有交易手续费,需要调整判断条件
5. 时间复杂度的误解
- 虽然算法是O(n),但要理解这依赖于"可以无限次交易"的前提
- 不要误以为所有股票问题都能用O(n)解决
题目的启发
1. 贪心算法的核心思想
- 局部最优 = 全局最优:当每个局部最优选择都能导向全局最优时,贪心策略有效
- 识别贪心适用场景:问题具有贪心选择性质和最优子结构
- 简化复杂问题:将看似复杂的连续持有问题分解为独立的每日决策
2. 问题建模的重要性
- 重新理解问题本质:最大利润 = 所有上涨区间的收益之和
- 数学转化:将连续区间收益转化为相邻天数差值的累加
- 抽象思维:忽略具体的买卖时机,关注收益的本质来源
3. 算法设计的层次性
这个问题展示了股票买卖问题的不同变种:
- 简单版本(本题):无限次交易 → 贪心 O(n)
- 中等版本:最多2次交易 → 动态规划 O(n)
- 困难版本:最多k次交易 → 动态规划 O(nk)
- 复杂版本:含冷冻期/手续费 → 状态机DP
4. 实际应用价值
- 投资策略启示:在理想条件下(无手续费、完美信息),应该抓住每一个上涨机会
- 现实约束的重要性:实际交易中的手续费、税费、信息不对称等因素会改变最优策略
- 理论与实践的差距:算法假设完美预测未来价格,现实中需要风险管理
5. 编程思维的培养
- 从暴力到优化:可以先思考暴力解法(枚举所有买卖组合),再寻找优化空间
- 模式识别:识别出"累加所有正差值"的模式
- 代码简洁性:最优解往往是最简洁的,复杂的逻辑可能意味着没有找到问题本质
6. 扩展思考
这个题目还可以引申出更多有趣的变种:
- 如果只能进行一次交易?
- 如果有交易手续费?
- 如果卖出后有1天冷冻期?
- 如果最多持有k股?
- 如果价格是实时流式数据?
这些问题的解决思路都建立在对基础问题的深入理解之上,体现了算法学习的递进性。
