【简单】121. 买卖股票的最佳时机

0 阅读3分钟

给定一个数组 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.length <= 105
  • 0 <= prices[i] <= 104

1. 生活案例:穿越时空的“后悔药”

想象你有一张某种股票的历史价格清单,你的目标很简单:低买高卖,大赚一笔。

  • 规则

    • 你只能买一次,卖一次。
    • 你必须先买入,才能卖出(不能穿越回过去卖股票)。
  • 策略

    • 当你站在第 i 天时,你就在想:“如果我今天卖掉,我能赚多少?”
    • 要让今天卖掉赚得最多,你只需要知道过去这几天里哪天的价格最低
    • 你每天都在心里记下“历史最低价”,然后算算“今天卖出的利润”,如果比之前的纪录高,就更新你的“最高利润”。

例子:价格是 [7, 1, 5, 3, 6, 4]

  • 第一天价格 7,最低价记为 7,利润 0。
  • 第二天价格 1,发现比 7 低,最低价更新为 1。
  • 第三天价格 5,如果今天卖,利润 5−1=4。
  • ……
  • 第五天价格 6,如果今天卖,利润 6−1=5(这是目前的最高纪录)。

2. 代码解析与“生活化”注释

这段代码非常高效,它只需要遍历一遍价格数组,就像你在看股票走势图一样。

JavaScript

/**
 * @param {number[]} prices - 每天的股票价格
 * @return {number} - 能获得的最大利润
 */
var maxProfit = function(prices) {
    let maxProfit = 0;   // 记录你见过的最高利润纪录
    let n = prices.length;
    let minPrice = Infinity; // 记录到目前为止的“历史最低价”,初始设为无穷大

    // 每天都去市场观察一下
    for (let i = 0; i < n; i++) {
        // 生活化解释:
        // 1. 更新历史最低点:
        // 如果今天的价格比我之前记下的最低价还便宜,那我就把最低价记为今天的
        if (prices[i] < minPrice) {
            minPrice = prices[i];
        }

        // 2. 计算如果今天卖掉能赚多少:
        // 用今天的价格减去我之前记下的历史最低价
        let curProfit = prices[i] - minPrice;

        // 3. 更新最高利润纪录:
        // 如果今天的利润打破了历史纪录,就记下来
        maxProfit = (curProfit > maxProfit) ? curProfit : maxProfit;
    }

    return maxProfit; // 返回我这辈子能靠这一单赚到的最多钱
};

3. 为什么代码这样写?(核心思维)

  1. 一次遍历 (O(n)) :你不需要去比较每一对买入卖出的组合(那样会变成 O(n2)),你只需要一边走一边记住“最便宜的时候”即可。

  2. 空间优化 (O(1)) :你只用了 maxProfitminPrice 两个变量。这比开辟一个 dp 数组要节省空间得多。

  3. 动态规划的简化:这其实是一个极简的 DP。

    • 状态:minPrice(到 i 天为止的最小买入价)。
    • 转移:minPrice = min(minPrice, prices[i])

总结

这道题教给我们:不需要预测未来,只需要记住过去最好的机会,并看看今天是否是收获的最佳时机。