跳至主要內容

买卖股票的最佳时机II

周子力大约 6 分钟教学文档动态规划

题目描述

picture 1
picture 1

题目分析

股票买卖最大利润问题分析

题目理解

首先,我们需要明确题目的关键约束条件:

  1. 最多只能持有一股股票
  2. 可以在同一天多次买卖(这意味着可以在同一天先卖出再买入)
  3. 目标是获得最大利润

贪心策略分析

由于我们可以在同一天多次交易,这意味着我们实际上可以"跳过"任何会导致亏损的持有期。

考虑价格序列:如果明天的价格比今天高,那么今天买入明天卖出就能获利。由于没有交易次数限制,我们可以把所有上涨的区间都抓住。

关键洞察:最大利润等于所有价格上升段的利润之和。

例如,价格序列为 [1, 2, 3, 4]:

  • 如果我们从第1天持有到第4天:利润 = 4 - 1 = 3
  • 如果我们每天都买卖:(2-1) + (3-2) + (4-3) = 1 + 1 + 1 = 3

两种策略结果相同!这说明我们可以将连续的上涨分解为每天的上涨来处理。

算法思路

遍历价格数组,只要发现明天的价格比今天高,就进行一次买卖(今天买,明天卖),将利润累加。

这样做的正确性基于:

  1. 所有正收益的交易都应该执行
  2. 由于可以同天多次交易,我们可以完美捕捉每一个上涨区间
  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. 最优子结构:全局最优解包含局部最优解。每个上涨区间都应该被利用。

  2. 贪心选择性质:在每个决策点(每一天),如果明天价格更高,立即买入是局部最优选择,且不会影响后续的最优选择。

  3. 无后效性:由于可以同天多次交易,今天的决策不会限制明天的选择。

注意事项与题目启发

需要注意的问题

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股?
  • 如果价格是实时流式数据?

这些问题的解决思路都建立在对基础问题的深入理解之上,体现了算法学习的递进性。

上次编辑于:
贡献者: zilizhou