目录
题目描述
给定一个数组 prices
,其中 prices[i]
表示某股票第 i
天的价格。你只能选择 某一天 买入,并在 未来的某一不同日子 卖出。请计算这笔交易的最大利润。若不能获取利润,返回 0
。
示例:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(价格 = 1)买入,第 5 天(价格 = 6)卖出,利润 = 6-1 = 5。
解题思路
核心思想:贪心策略
我们需要找到最低价买入,并在之后的最高价卖出。但如何高效实现这一点?
-
关键观察:假设在第
i
天卖出,则买入价一定是前i
天中的最低价。 -
贪心策略:遍历数组时,维护一个历史最低价
minPrice
,并计算每天卖出时的利润,更新最大利润。
代码实现
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function (prices) {
let maxProfit = 0; // 最大利润
let minPrice = prices[0]; // 最小价格
for (let i = 1; i < prices.length; i++) {
// 如果当前价格小于最小价格,则更新最小价格
if (prices[i] < minPrice) {
minPrice = prices[i];
}
// 如果当前价格大于最小价格,则计算利润 (当前价格 - 最小价格)
maxProfit = Math.max(maxProfit, prices[i] - minPrice);
}
return maxProfit;
};
console.log(maxProfit([7, 1, 5, 3, 6, 4]));
算法解析
步骤拆解
-
初始化:
-
maxProfit = 0
:初始最大利润为0(确保无盈利时返回0)。 -
minPrice = prices[0]
:初始最低价为第1天的价格。
-
-
遍历数组:
-
从第2天开始,依次比较每一天的价格。
-
更新最低价:如果当前价格比
minPrice
低,则更新minPrice
。 -
计算利润:如果当前价格高于
minPrice
,则计算当前卖出利润,并与maxProfit
比较,取较大值。
-
示例演示
以输入 [7,1,5,3,6,4]
为例:
天数 | 价格 | minPrice | 当前利润 | maxProfit |
---|---|---|---|---|
1 | 7 | 7 | 0 | 0 |
2 | 1 | 1 | 0 | 0 |
3 | 5 | 1 | 4 | 4 |
4 | 3 | 1 | 2 | 4 |
5 | 6 | 1 | 5 | 5 |
6 | 4 | 1 | 3 | 5 |
最终最大利润为 5。
正确性证明
-
最优子结构:
-
最大利润必然产生于某个最低价之后的高价。
-
维护
minPrice
确保能捕捉到所有可能的买入点。
-
-
遍历覆盖所有可能性:
-
每一天都尝试以当日价格卖出,并基于历史最低价计算利润,因此不会遗漏最优解。
-
复杂度分析
-
时间复杂度:O(n),仅需一次遍历。
-
空间复杂度:O(1),仅用常数变量存储状态。
边界情况处理
-
价格递减数组:如
[7,6,5,4,3]
,最大利润为0。 -
单一价格数组:如
[5,5,5]
,利润为0。 -
数组长度为1:无法交易,直接返回0。
其他解法对比
暴力法(不可取)
-
思路:双重循环遍历所有买入卖出组合,计算最大利润。
-
缺点:时间复杂度 O(n²),在数据量大时超时。
动态规划
-
思路:定义状态
dp[i]
表示前i
天的最大利润。 -
状态转移:
dp[i] = max(dp[i-1], prices[i] - minPrice)
。 -
分析:本质上与贪心策略一致,但贪心法无需额外空间。
总结
贪心策略通过维护历史最低价,将问题转化为一次遍历,高效解决了一次交易的最大利润问题。
关键点:
-
理解最低价对后续利润的决定性作用。
-
遍历过程中不断更新最低价和最大利润。
此方法简洁高效,是同类问题中的经典解法。对于多次交易的问题(如LeetCode 122题),需采用不同的策略。