给定一个数组 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 <= 1050 <= 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. 为什么代码这样写?(核心思维)
-
一次遍历 (O(n)) :你不需要去比较每一对买入卖出的组合(那样会变成 O(n2)),你只需要一边走一边记住“最便宜的时候”即可。
-
空间优化 (O(1)) :你只用了
maxProfit和minPrice两个变量。这比开辟一个dp数组要节省空间得多。 -
动态规划的简化:这其实是一个极简的 DP。
- 状态:
minPrice(到 i 天为止的最小买入价)。 - 转移:
minPrice = min(minPrice, prices[i])。
- 状态:
总结
这道题教给我们:不需要预测未来,只需要记住过去最好的机会,并看看今天是否是收获的最佳时机。