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

相关推荐
养军博客2 小时前
C语言五天速成(可用于蓝桥杯备考 难度中等偏下)
c语言·算法·蓝桥杯
爱尔兰极光2 小时前
LeetCode--有序数组的平方
算法·leetcode·职场和发展
jay神2 小时前
森林火灾检测数据集
算法·机器学习·目标跟踪
80530单词突击赢2 小时前
STLVector底层原理与高效运用
数据结构·算法
haluhalu.2 小时前
LeetCode---基础算法刷题指南
数据结构·算法·leetcode
iAkuya2 小时前
(leetcode)力扣100 58组合总和(回溯)
算法·leetcode·职场和发展
80530单词突击赢2 小时前
C++关联容器深度解析:set/map全攻略
java·数据结构·算法
m0_561359672 小时前
代码热更新技术
开发语言·c++·算法
Warren982 小时前
Pytest Fixture 作用域详解:Function、Class、Module、Session 怎么选
面试·职场和发展·单元测试·pytest·pip·模块测试·jira