📌 题目链接:121. 买卖股票的最佳时机 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:数组、贪心、动态规划思想、一次遍历
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
🧠 题目分析
本题要求在仅允许一次买入和一次卖出的前提下,找出能获得的最大利润。关键约束如下:
- 必须先买入再卖出(即
i < j); - 只能交易一次(不能多次买卖);
- 若无法获利(如价格持续下跌),则返回
0。
这看似是一个“找最大差值”的问题,但差值必须满足后项 > 前项且索引递增,因此不能简单用 max - min。
💡 面试高频点:此题是“股票系列”第一题,后续还有 2~6 道变体(允许多次交易、含手续费、冷冻期等),掌握本题是理解整个系列的基础!
⚙️ 核心算法及代码讲解
✅ 核心思想:贪心 + 一次遍历
我们不需要知道具体哪天买、哪天卖,只需在遍历过程中:
- 记录到目前为止的最低价格(minprice) —— 这代表“如果我在今天之前买,最便宜是多少”;
- 计算今天卖出能获得的利润(price - minprice) ;
- 更新全局最大利润(maxprofit) 。
🌟 为什么这是贪心?
因为我们每一步都做出局部最优选择:假设“以历史最低价买入”,然后看今天卖出是否更优。虽然我们不知道未来价格,但通过不断更新minprice和maxprofit,最终能得到全局最优解。
📌 与动态规划的关系:
此解法可视为 DP 的空间优化版本。标准 DP 定义dp[i][0/1]表示第 i 天持有/不持有股票的最大利润,但本题状态转移只依赖前一天,故可压缩为两个变量。
🔍 代码逐行注释(C++)
class Solution {
public:
int maxProfit(vector<int>& prices) {
// 初始化 minprice 为一个极大值(比题目中 prices[i] <= 1e4 更大)
int inf = 1e9;
int minprice = inf; // 记录遍历到当前位置时的历史最低价格
int maxprofit = 0; // 记录全局最大利润
// 遍历每一天的价格
for (int price : prices) {
// 先尝试在今天卖出:利润 = 当前价格 - 历史最低买入价
maxprofit = max(maxprofit, price - minprice);
// 再更新历史最低价(注意顺序!必须先计算利润再更新 minprice)
minprice = min(price, minprice);
}
return maxprofit;
}
};
⚠️ 关键细节:
必须先计算maxprofit,再更新minprice!
如果顺序颠倒,会导致当天价格既作为买入又作为卖出(即price - price = 0),虽然不影响结果(因为maxprofit不会变小),但逻辑上错误。更重要的是,在某些变体题中(如限制交易次数),顺序错误会导致严重 bug。
🧩 解题思路(分步拆解)
-
初始化:设
minprice = +∞(确保第一天价格能更新它),maxprofit = 0。 -
遍历每一天:
- 步骤 A:假设今天卖出,计算利润
price - minprice,并更新maxprofit。 - 步骤 B:将今天的
price与minprice比较,更新历史最低价。
- 步骤 A:假设今天卖出,计算利润
-
返回结果:遍历结束后,
maxprofit即为答案。
📊 以示例 [7,1,5,3,6,4] 为例:
Day Price minprice (before update) profit (price - minprice) maxprofit minprice (after update) 0 7 ∞ 7 - ∞ → 负数(忽略) 0 7 1 1 7 1 - 7 = -6 0 1 2 5 1 5 - 1 = 4 4 1 3 3 1 3 - 1 = 2 4 1 4 6 1 6 - 1 = 5 5 1 5 4 1 4 - 1 = 3 5 1 ✅ 最终
maxprofit = 5,正确!
📈 算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n) :仅需一次遍历数组,每个元素访问一次。 |
| 空间复杂度 | O(1) :仅使用两个额外变量 minprice 和 maxprofit。 |
| 稳定性 | 稳定,无边界问题(因 maxprofit 初始为 0,即使全下跌也返回 0)。 |
| 扩展性 | 此思想可推广至“最多 k 次交易”等问题(需结合 DP)。 |
💼 面试加分项:
- 能说出“这是贪心,因为每一步都基于当前最优假设”;
- 能对比暴力法(O(n²))并说明为何一次遍历足够;
- 能指出“顺序不能颠倒”的细节;
- 能联系到后续股票问题(如 #122 无限次交易可用贪心累加正差值)。
💻 代码实现
C++(保留原模板结构)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price : prices) {
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
vector<int> prices1 = {7,1,5,3,6,4};
cout << sol.maxProfit(prices1) << "\n"; // 输出: 5
vector<int> prices2 = {7,6,4,3,1};
cout << sol.maxProfit(prices2) << "\n"; // 输出: 0
return 0;
}
JavaScript
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let minprice = Infinity;
let maxprofit = 0;
for (const price of prices) {
maxprofit = Math.max(maxprofit, price - minprice);
minprice = Math.min(price, minprice);
}
return maxprofit;
};
// 测试
console.log(maxProfit([7,1,5,3,6,4])); // 5
console.log(maxProfit([7,6,4,3,1])); // 0
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!