力扣算法 121. 买卖股票的最佳时机

力扣算法 121. 买卖股票的最佳时机

1. 题目描述

  • 题目:

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

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

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

  • 输入: 整数数组 pricesprices[i] 表示第 i 天的股票价格

  • 输出: 你最多完成 一次 交易(买入一次 + 卖出一次)能获得的最大利润

  • 要求:

    • 必须先买后卖(卖出日要在买入日之后)

    • 如果不能获得利润,返回 0

  • 题目关键点:

    1. 只能交易一次(不是多次买卖)

    2. 卖出必须发生在买入之后(时间顺序约束)

    3. 需要最大化 prices[j] - prices[i]j > i

示例

输入:[7,1,5,3,6,4]

输出:5

解释:第 2 天买入(1),第 5 天卖出(6),利润 6 - 1 = 5


2. 解题思路(先讲人话,再讲算法)

我把题目翻译成大白话就是:

我只能选一个"最低的买入价"(且在前面出现),再在它后面找一个"最高的卖出价",让差值最大。

然后我们要做到:

  1. 从左到右遍历时,随时记住到目前为止最低价格 minPrice(最佳买入点候选)

  2. 每到一天价格 price,就尝试把它当成"卖出价",更新最大利润:maxProfit = max(maxProfit, price - minPrice)


3. 题解(核心算法)

3.1 为什么用这个方法(为什么不用别的)

  • 暴力法 会枚举所有买入日 i 和卖出日 jj>i),时间复杂度 O(n^2)n 最大可到 10^5,会直接超时。

  • 贪心/一次遍历 的关键观察:

    当我们遍历到第 i 天时,若想在这一天卖出,最佳买入一定是 之前出现过的最低价

    所以只要维护一个 minPrice,就能在 O(n) 内完成。

这题也可以用 DP 理解,但实现上就是"维护历史最低价 + 维护最大差值"。

3.2 关键变量定义(读代码前先认清角色)

  • minPrice:从第 0 天到当前天为止出现过的最低价格(最佳买入候选)

  • maxProfit:目前为止能得到的最大利润

  • price:当天价格(遍历时的当前值)

3.3 代码(完整可运行,Java)

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        // 边界:题目保证 length >= 1,但写上更稳
        if (prices == null || prices.length == 0) return 0;

        int minPrice = prices[0];  // 历史最低价(买入价)
        int maxProfit = 0;         // 最大利润(不亏就至少为 0)

        for (int i = 1; i < prices.length; i++) {
            int price = prices[i];

            // 1) 尝试今天卖出:利润 = 今天价格 - 历史最低价
            maxProfit = Math.max(maxProfit, price - minPrice);

            // 2) 更新历史最低价:为后续卖出准备更低的买入点
            minPrice = Math.min(minPrice, price);
        }

        return maxProfit;
    }
}

4. 代码逐段/逐行讲解(最重要)

4.1 初始化部分

  • int minPrice = prices[0];

    第一天如果买入,那买入价就是 prices[0]。我们先把它当作当前最低价。

  • int maxProfit = 0;

    因为允许"不交易",且不能亏钱,所以最大利润最小为 0。

4.2 主循环部分

  • for (int i = 1; i < prices.length; i++)

    从第二天开始遍历,因为第一天已经用于初始化最低价。

  • maxProfit = Math.max(maxProfit, price - minPrice);
    把今天当作卖出日 :如果之前最低价买入,今天卖出能赚多少?

    用它尝试更新最大利润。

  • minPrice = Math.min(minPrice, price);
    更新最低价:今天价格如果更低,就可能成为未来更好的买入点。

记忆口诀:
先算"今天卖出能赚多少",再更新"历史最低买入价"。

4.3 返回值部分

  • return maxProfit;

    遍历完后,记录的就是全局最大利润;如果一直没赚到,仍是 0。


5. 本题相关知识点补充

5.1 贪心的核心思想(本题为什么天然贪心)

本题的"卖出价"每天都在变化,但"如果今天卖出,最优买入一定是历史最低价"。

所以我们只需要维护一个历史最小值,不需要回头枚举所有组合。

5.2 动态规划视角(帮助你建立模型)

你也可以把它理解成 DP:

  • 定义:dp[i] 表示到第 i 天为止的最大利润

  • 转移:
    dp[i] = max(dp[i-1], prices[i] - minPriceSoFar)

  • 其中 minPriceSoFarmin(prices[0..i])

最终 dp[n-1] 就是答案。

实现时我们把 dp 压缩掉,只保留 maxProfitminPrice,就是上面的写法。

5.3 易错点

  1. 把"最低价"更新顺序写反:推荐"先用今天价格计算利润,再更新 minPrice",逻辑更清晰

  2. 误以为可以买卖多次:本题只允许一次交易(多次的是 122)

  3. 忘了不能亏钱:因此答案至少是 0,而不是负数


6. 复杂度分析

  • 时间复杂度: O(n)(只遍历一遍数组)

  • 空间复杂度: O(1)(只用常数变量)


7. 总结

本题一句话总结:

遍历价格数组,维护"历史最低买入价 minPrice",每一天都尝试作为卖出日更新最大利润。

你学到的技巧:

  • 用"历史最小值"把 O(n^2) 枚举优化成 O(n)

  • 贪心 / DP(状态压缩)在这种"前缀最优"问题里非常常见

类似题推荐(股票系列刷题路线):

  • 122:买卖股票的最佳时机 II(可多次交易)

  • 123:买卖股票的最佳时机 III(最多两次交易)

  • 309:最佳买卖股票时机含冷冻期

  • 714:买卖股票的最佳时机含手续费


8. 扩展写法(可选)------暴力法(理解用,提交会超时)

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int maxProfit = 0;
        for (int i = 0; i < prices.length - 1; i++) {
            for (int j = i + 1; j < prices.length; j++) {
                maxProfit = Math.max(maxProfit, prices[j] - prices[i]);
            }
        }
        return maxProfit;
    }
}

为什么超时:两层循环会做约 n(n-1)/2 次比较,n=10^5 时数量级太大。

相关推荐
Jason_Honey210 分钟前
【平安Agent算法岗面试-二面】
人工智能·算法·面试
程序员酥皮蛋21 分钟前
hot 100 第三十五题 35.二叉树的中序遍历
数据结构·算法·leetcode
追随者永远是胜利者24 分钟前
(LeetCode-Hot100)207. 课程表
java·算法·leetcode·go
香芋Yu1 小时前
【大模型面试突击】08_推理范式与思维链
面试·职场和发展
仰泳的熊猫1 小时前
题目1535:蓝桥杯算法提高VIP-最小乘积(提高型)
数据结构·c++·算法·蓝桥杯
那起舞的日子2 小时前
动态规划-Dynamic Programing-DP
算法·动态规划
闻缺陷则喜何志丹2 小时前
【前后缀分解】P9255 [PA 2022] Podwyżki|普及+
数据结构·c++·算法·前后缀分解
每天吃饭的羊2 小时前
时间复杂度
数据结构·算法·排序算法
小李独爱秋3 小时前
模拟面试:用自己的话解释一下lvs的工作原理
linux·运维·面试·职场和发展·操作系统·lvs
ValhallaCoder3 小时前
hot100-堆
数据结构·python·算法·