leetcode 面试经典 150 题(7/150) 121.买卖股票的最佳时机

114 阅读5分钟

题目描述

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

算法思路

单次遍历寻找最优买卖点

我们的目标是找到最大利润。利润的计算方式是 卖出价格 - 买入价格。同时,题目明确指出,买入必须发生在卖出之前。

初始想法:暴力枚举

最直接的想法,就是尝试所有可能的买入和卖出组合。

  1. 确定买入日期:遍历 prices 数组,每一天都可以作为买入的日期。
  2. 确定卖出日期:对于每一个买入日期,再遍历其之后的所有日期,作为可能的卖出日期。
  3. 计算利润:如果卖出价格高于买入价格,计算利润,并更新最大利润。

这种思路是可行的,但效率不高。如果价格数组很长,我们需要进行两层循环,时间复杂度会达到 O(n^2)。

优化思考:能否只遍历一次?

暴力枚举效率不高,主要原因是做了很多重复的计算:对于每个买入日期,都要遍历后面的所有卖出日期。

关键点在于:只需要找到一个最低的买入价格,以及之后最高的卖出价格。但是,我们不能事先知道哪天是最低买入价,哪天是最高卖出价。

也就是说,目标是找到两个元素 prices[i] 和 prices[j],满足 i < j,并且它们的差值 prices[j] - prices[i] 最大。

实际上,我们不必固定买入日期再去寻找卖出日期,我们可以在遍历价格数组的过程中,同时更新当前的最低买入价格最大利润

核心思想:动态维护最低买入价格和最大利润

  1. 初始化
    • maxProfit 初始化为 0,因为如果没有交易,利润为 0。
    • cost (买入成本) 初始化为 prices[0],假设第一天买入,这是一个初始的“最低买入价格”。 当然,更严谨的做法是将 cost 初始化为一个非常大的数,比如正无穷,但考虑到题目 prices 数组的特性,初始化为 prices[0] 在本题场景下是可行的,而且更简洁。
  2. 遍历数组 prices:从头到尾遍历价格数组,对于每一天的价格 price
    • 计算当前利润: 计算如果今天卖出,并且以之前的 cost 买入,能获得的利润 price - cost
    • 更新最大利润: 将当前利润与 maxProfit 比较,取较大值更新 maxProfit。 这样保证 maxProfit 始终记录遍历到目前为止的最大利润。
    • 更新最低买入成本: 将当前价格 pricecost 比较,取较小值更新 cost。 因为我们希望买入成本尽可能低,所以 cost 始终记录遍历到目前为止的最低价格。
  3. 返回结果: 遍历结束后,maxProfit 中存储的就是我们能获得的最大利润。

步骤解析

  1. 初始化最大利润 maxProfit = 0 和最低买入成本 cost = prices[0]
  2. 遍历价格数组 prices 中的每个价格 price
  3. 计算当前利润:price - cost
  4. 更新最大利润:maxProfit = max(maxProfit, price - cost)
  5. 更新最低买入成本:cost = min(cost, price)
  6. 遍历结束后,返回 maxProfit

复杂度分析

  • 时间复杂度:O(n),只需要一次遍历价格数组。
  • 空间复杂度:O(1),只使用了常数个额外变量 (maxProfit, cost)。

代码实现

func maxProfit(prices []int) int {
    maxProfit := 0
    cost := prices[0] // 初始化买入成本为第一天的价格
    for _, price := range prices {
        maxProfit = max(maxProfit, price-cost) // 更新最大利润
        cost = min(cost, price)             // 更新最低买入成本
    }
    return maxProfit
}

示例解析

prices = [7,1,5,3,6,4] 为例:

  1. 初始状态maxProfit = 0, cost = 7

  2. 遍历过程

    • 第1天 (价格 7): price = 7maxProfit = max(0, 7-7) = 0cost = min(7, 7) = 7
    • 第2天 (价格 1): price = 1maxProfit = max(0, 1-7) = 0cost = min(7, 1) = 1
    • 第3天 (价格 5): price = 5maxProfit = max(0, 5-1) = 4cost = min(1, 5) = 1
    • 第4天 (价格 3): price = 3maxProfit = max(4, 3-1) = 4cost = min(1, 3) = 1
    • 第5天 (价格 6): price = 6maxProfit = max(4, 6-1) = 5cost = min(1, 6) = 1
    • 第6天 (价格 4): price = 4maxProfit = max(5, 4-1) = 5cost = min(1, 4) = 1
  3. 最终结果maxProfit = 5

关键点

  • 一次遍历完成: 巧妙地使用单次遍历,动态更新最低买入成本和最大利润,将时间复杂度降到 O(n)。
  • 贪心思想: 每一步都做出当前看来最优的选择(保持最低买入成本,更新最大利润),最终得到全局最优解。
  • 理解 cost 的作用: cost 变量至关重要,它记录了遍历到当前天数为止的最低价格,为后续计算利润提供了基准。