力扣算法 121. 买卖股票的最佳时机
1. 题目描述
-
题目:
给定一个数组
prices,它的第i个元素prices[i]表示一支给定股票第i天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回
0。 -
输入: 整数数组
prices,prices[i]表示第i天的股票价格 -
输出: 你最多完成 一次 交易(买入一次 + 卖出一次)能获得的最大利润
-
要求:
-
必须先买后卖(卖出日要在买入日之后)
-
如果不能获得利润,返回
0
-
-
题目关键点:
-
只能交易一次(不是多次买卖)
-
卖出必须发生在买入之后(时间顺序约束)
-
需要最大化
prices[j] - prices[i]且j > i
-
示例
输入:[7,1,5,3,6,4]
输出:5
解释:第 2 天买入(1),第 5 天卖出(6),利润 6 - 1 = 5
2. 解题思路(先讲人话,再讲算法)
我把题目翻译成大白话就是:
我只能选一个"最低的买入价"(且在前面出现),再在它后面找一个"最高的卖出价",让差值最大。
然后我们要做到:
-
从左到右遍历时,随时记住到目前为止最低价格
minPrice(最佳买入点候选) -
每到一天价格
price,就尝试把它当成"卖出价",更新最大利润:maxProfit = max(maxProfit, price - minPrice)
3. 题解(核心算法)
3.1 为什么用这个方法(为什么不用别的)
-
暴力法 会枚举所有买入日
i和卖出日j(j>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) -
其中
minPriceSoFar是min(prices[0..i])
最终 dp[n-1] 就是答案。
实现时我们把 dp 压缩掉,只保留 maxProfit 与 minPrice,就是上面的写法。
5.3 易错点
-
把"最低价"更新顺序写反:推荐"先用今天价格计算利润,再更新 minPrice",逻辑更清晰
-
误以为可以买卖多次:本题只允许一次交易(多次的是 122)
-
忘了不能亏钱:因此答案至少是 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 时数量级太大。