题目描述
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择某一天买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(价格=1)买入,第 5 天(价格=6)卖出,利润=6-1=5
解法1:暴力破解法(双重循环)
核心思想:通过遍历所有可能的买入和卖出组合,找出最大利润差,这也是最简单常用的方法。
function maxProfit(prices) {
let max = 0;
for (let i = 0; i < prices.length; i++) {
for (let j = i + 1; j < prices.length; j++) {
const profit = prices[j] - prices[i];
if (profit > max) {
max = profit;
}
}
}
return max;
}
解题思路:
- 首先初始化最大利润
max为0,通过外层循环遍历所有可能的买入日( i 从0 到 n-1) - 通过内层循环遍历该买入日之后的所有卖出日( j 从 i+1到 n-1 )
- 计算每个买卖组合的利润 = prices[j] - prices[i],如果利润大于当前最大利润,则更新最大利润
- 返回最终的最大利润值
时间复杂度:O(n²)
空间复杂度:O(1)
注意点:
1. 你必须要确保 j > i,即卖出日永远在买入日之后。
2. 题目要求不能获取利润时返回0,无需额外处理。
3. 当 prices 为空时返回0。
4. 数据量大时效率极低(1000个元素需500万次计算)。
解法2:动态规划法(单次遍历)
核心思想:在遍历过程中记录历史最低价,并计算当前卖出可能的最大利润。
function maxProfit(prices) {
if (prices.length === 0) return 0;
let minPrice = prices[0];
let maxProfit = 0;
for (let i = 1; i < prices.length; i++) {
// 更新历史最低价
if (prices[i] < minPrice) {
minPrice = prices[i];
}
// 计算当前卖出利润并更新最大利润
else if (prices[i] - minPrice > maxProfit) {
maxProfit = prices[i] - minPrice;
}
}
return maxProfit;
}
解题思路:
- 初始化:设置最低价
minPrice为第一天的价格,最大利润maxProfit为0 - 遍历更新:
- 如果当前价格低于历史最低价: 更新历史最低价
minPrice为当前价格。 - 否则则计算 当前利润 = 当前价格 - 历史最低价,如果当前利润大于最大利润,更新最大利润
maxProfit
- 返回结果:遍历结束后的
maxProfit即为答案
时间复杂度:O(n)
空间复杂度:O(1)
注意点:
1. minPrice 应初始化为 prices[0] 而非 Infinity
2. 应从第2天(也就是索引为1)的时候开始遍历
3. 不能在同一天同时更新 minPrice 和 maxProfit
4. 遗漏 prices.length === 0 检查
解法3:动态规划法(状态机)
核心思想:明确定义持有和未持有股票两种状态,使用状态转移方程计算最大利润。
function maxProfit(prices) {
let dp0 = 0; // dp0: 未持有股票时的最大利润
let dp1 = -prices[0]; // dp1: 持有股票时的最大利润
for (let i = 1; i < prices.length; i++) {
// 保持未持有 或 卖出股票
dp0 = Math.max(dp0, dp1 + prices[i]);
// 保持持有 或 买入股票(只能买一次所以是 -prices[i])
dp1 = Math.max(dp1, -prices[i]);
}
return dp0;
}
状态定义:
dp0:第i天结束时未持有股票的最大利润dp1:第i天结束时持有股票的最大利润
状态转移:
dp0 = max(保持未持有, 卖出股票)= max(dp0, dp1 + prices[i])dp1 = max(保持持有, 买入股票)= max(dp1, -prices[i])(因为只能交易一次)
时间复杂度:O(n)
空间复杂度:O(1)
优点:框架化思维,容易扩展到其他更复杂的股票问题
解法4:差值数组(最大子序和)
核心思想:将价格数组转换为每日价格变化,问题转化为求最大子序和。
function maxProfit(prices) {
if (prices.length < 2) return 0;
// 计算每日价格变化
const diff = [];
for (let i = 1; i < prices.length; i++) {
diff.push(prices[i] - prices[i - 1]);
}
// 求最大子序和(Kadane算法)
let maxCur = 0;
let maxSoFar = 0;
for (let i = 0; i < diff.length; i++) {
maxCur = Math.max(0, maxCur + diff[i]);
maxSoFar = Math.max(maxSoFar, maxCur);
}
return maxSoFar;
}
数学原理:
第i天买入,第j天卖出的利润:
prices[j] - prices[i]
= (prices[i+1]-prices[i]) + ... + (prices[j]-prices[j-1])
= ∑每日差值
时间复杂度:O(n)
空间复杂度:O(n)(可优化为O(1))
上面的代码还能进一步优化(空间O(1)):
function maxProfit(prices) {
let maxCur = 0;
let maxSoFar = 0;
for (let i = 1; i < prices.length; i++) {
maxCur = Math.max(0, maxCur + prices[i] - prices[i-1]);
maxSoFar = Math.max(maxSoFar, maxCur);
}
return maxSoFar;
}
总结
算法选择:
- 最优解法:单次遍历法(解法2),O(n)时间,O(1)空间
- 扩展性:状态机法(解法3),适用于更复杂的股票问题
- 学术价值:差值法(解法4),展示问题转化的技巧