力扣算法 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 时数量级太大。

相关推荐
Jerry3 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry5 小时前
LeetCode 459. 重复的子字符串
算法
海石8 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石8 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry10 小时前
LeetCode 151. 反转字符串中的单词
算法
咖啡星人k12 小时前
从Vibe Coding到Prompt Engineering:AI编程的新技能树
面试·职场和发展·ai编程
a11177612 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜12 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry12 小时前
LeetCode 541. 反转字符串 II
算法
Jerry13 小时前
LeetCode 344. 反转字符串
算法