👋 欢迎阅读

🎯 欢迎来到「买卖股票的最佳时机」题解之旅! 本文将带你从“寻找最佳买入卖出时机”这一投资场景出发,深入理解贪心 + 一次遍历的简洁解法.
在开始之前,建议你先:
-
了解题目背景:这是 LeetCode 121 题,给定股票每日价格数组
prices,你只能买卖一次(先买后卖),求能获得的最大利润。本质上是找出一对 (买入日, 卖出日),使得卖出价与买入价的差值最大,且买入日在卖出日之前。 -
明确学习目标:掌握如何用贪心法维护「历史最低价格」和「当前最大利润」,理解为什么只需一趟遍历即可得到最优解,并熟练处理价格下跌无利润(返回
0)的边界情况。 -
准备好环境:建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如
prices = [7,1,5,3,6,4]输出5)。
本文将从问题转化、贪心策略设计、双变量模拟到代码实现,层层递进。即使你对贪心算法还不熟悉,我们也会从“每天更新最低买入价,并计算当天卖出的利润”这一直觉出发,让你轻松抓住核心思想——最低点买入,最高点卖出(但卖出要在买入之后)。现在,让我们一起在股票价格的起伏中抓住最佳交易时机吧! 📈💰
一、题目

二、做题思路
1. 问题分析(前置分析)
只能在 某一天买入,并在 未来的某一天卖出,求最大利润。核心是:对于每个卖出日,买入价应当是 之前所有天中的最低价格,这样当天的利润才最大。因此只需在遍历过程中维护历史最低价,并不断计算当前价格与历史最低价的差值,取最大值即可。
2. 贪心策略(核心决策规则)
-
初始化:
minPrice = prices[0](第一天价格作为初始最低价),maxProfit = 0。 -
从第二天开始遍历:
-
计算 当天利润 =
prices[i] - minPrice,更新maxProfit = max(maxProfit, 当天利润)。 -
更新 历史最低价
minPrice = min(minPrice, prices[i]),供后续天数使用。
-
-
遍历结束后,
maxProfit即为最大利润。
3. 正确性说明(简单版本)
对于每一天 i,若要在这一天卖出,那么最优的买入日必然是之前价格最低的那一天,这样利润最大。因此,只需在遍历中不断更新历史最低价,并计算当天卖出能获得的最大利润,就能覆盖所有可能的卖出时机。
4. 实现细节(边界防护)
-
数组长度至少为 1,初始化
minPrice为prices[0]。 -
从
i=1开始遍历,先更新maxProfit,再更新minPrice(顺序可互换,但先计算利润再更新最低价不影响结果)。 -
若价格一直下跌,
maxProfit保持为 0,符合题意(允许不交易)。
5. 返回值(目标映射)
返回 maxProfit,即能够获得的最大利润。
四、代码
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
// 贪心策略:在每一天,假设当天卖出股票,那么买入价应为之前所有天中的最低价,
// 这样当天的利润 = 当天价格 - 历史最低价,取所有天利润的最大值即为答案。
// 初始化:历史最低价为第一天的价格,最大利润初始为0(因为允许不交易)
int minPrice = prices[0]; // 截止当前日期之前的最低买入价
int maxProfit = 0; // 当前能获得的最大利润
// 从第二天开始遍历(第一天无法卖出,利润为0)
for (int i = 1; i &lt; prices.size(); i++)
{
// 计算如果今天卖出能获得的利润,并更新最大利润
maxProfit = max(maxProfit, prices[i] - minPrice);
// 更新历史最低价(为后续天数的卖出做准备)
minPrice = min(minPrice, prices[i]);
}
// 返回最大利润
return maxProfit;
}
};
五、流程图

六、正确性说明(详细版)
步骤 1:符号与问题建模
+----------------------------------------------------+
| 输入价格数组 prices,长度 n |
| 交易规则:只能买卖一次,卖出日在买入日之后 |
| 目标:最大化卖出价 - 买入价(若为负则取 0) |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 贪心策略:遍历每一天,假设当天卖出股票, |
| 买入价应为截止前一天为止的**历史最低价**。 |
| 更新最大利润 = max(最大利润, 当天价格 - 历史最低价) |
| 然后更新历史最低价 = min(历史最低价, 当天价格)。 |
| 贪心实质:每天选择之前最低价买入,是当天能获得的 |
| 最大利润,且该决策不会影响未来,因为买入价越低越好。|
+----------------------------------------------------+
-
设 minPrice 为截至当前日期(不包括当天)之前出现的最低价格。
-
设 maxProfit 为已遍历的天数中能获得的最大利润。
-
对每一天
i,若当天卖出,则最大可能利润为prices[i] - minPrice,取所有天数的最大值即为答案。 -
贪心选择性质:在任意一天卖出时,最优买入价必然是之前所有价格中的最小值,因为利润 = 卖出价 - 买入价,卖出价固定时,买入价越小利润越大。因此每天维护历史最低价是合理的。
步骤 2:关键性质 —— 最优解必包含某一天的历史最低价买入(反证法)
+----------------------------------------------------+
| 性质 1:对任意一天 i,若当天卖出,则最优买入价 |
| 必须是 min{prices[0..i-1]}。 |
| 反证:若买入价不是最低,则用更低价格替换买入价, |
| 利润会严格增大,且仍满足买入日早于卖出日,矛盾。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 性质 2:全局最优利润等于 max_{i} (prices[i] - |
| min_{j < i} prices[j])。 |
| 因为最优交易必然在某一天卖出,而对应的最优买入价 |
| 就是该天之前的最小值。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 性质 3:贪心算法在扫描过程中逐步计算了所有 i |
| 对应的上述表达式,并取最大值,因此不会遗漏任何 |
| 潜在的更优交易。 |
+----------------------------------------------------+
详细论证(结合反证法):
-
反证:假设在某个最优解中,卖出日为
i,但买入价不是prices[0..i-1]中的最小值,设最小值出现在j(j < i)且prices[j] < 当前买入价。用prices[j]替换原买入价,由于卖出价不变,新利润 =prices[i] - prices[j]>prices[i] - 原买入价,且j < i,满足交易条件,故原解不是最优,矛盾。因此,对于任意固定的卖出日,最优买入价一定是之前的最小值。 -
因此,全局最大利润必然等于所有卖出日对应的“当天价格 - 当日之前最低价”的最大值。贪心算法在遍历过程中,恰好通过
minPrice维护了每个位置之前的最小值,并计算了每个位置的候选利润,再取最大值。所以,贪心算法的结果与最优解定义完全一致,不会遗漏任何可能。
步骤 3:归纳证明 —— 扫描过程逐步得到正确答案
+----------------------------------------------------+
| 初始化:minPrice = prices[0],maxProfit = 0。 |
| 假设已经处理完前 k 个元素(索引 0..k-1), |
| minPrice 为前 k 个元素的最小值, |
| maxProfit 为前 k 个元素中能够获得的最大利润。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 处理第 k 个元素(索引 k): |
| ① 计算如果今天卖出,利润 = prices[k] - minPrice; |
| ② 更新 maxProfit = max(maxProfit, 利润); |
| ③ 更新 minPrice = min(minPrice, prices[k])。 |
| 更新后,minPrice 为前 k+1 个元素的最小值, |
| maxProfit 为前 k+1 个元素的最大利润, |
| 归纳假设保持。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 遍历结束后,maxProfit 即为所有天数的最大利润。 |
| 因为每一步都正确维护了前 i 个元素的最优值, |
| 最终结果与步骤 2 中的公式一致。 |
+----------------------------------------------------+
详细论证:
-
归纳基础:在遍历前,
minPrice = prices[0],maxProfit = 0。若数组长度为1,不能交易,返回0,正确。 -
归纳步骤:假设已经处理完前
i个元素(0..i-1),minPrice是它们的最小值,maxProfit是前i个元素内的最大利润(允许不交易)。现在考虑第i个元素(prices[i]):-
如果今天卖出,最大利润为
prices[i] - minPrice,因为minPrice是之前的最小买入价,由步骤2可知这是当天的最优卖出利润。 -
更新
maxProfit为原先最大值与今天候选值中的较大者,则maxProfit变为前i+1个元素的最大利润。 -
更新
minPrice为min(minPrice, prices[i]),则minPrice变为前i+1个元素的最小值。
-
-
由于归纳假设成立,遍历结束后,
maxProfit即整个数组的最大利润。若所有价格递减,则maxProfit始终为0,正确。
🎯 闭幕

🎉 恭喜你完成了「买卖股票的最佳时机」问题的学习!
为了巩固知识并进一步拓展,建议你:
🚀 动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。
💡 深入思考
-
本题采用 贪心策略:遍历每一天,假设当天卖出,则之前的最低买入价就是最优买入价。为什么记录“历史最低价”就能保证当前卖出的利润最大? 如果买入价不是历史最低,利润会更大还是更小?
-
代码中
maxProfit初始化为0,即使价格一直下跌,最后也会返回0(表示不交易)。如果题目要求必须进行一次交易(买入和卖出),初始化应该怎么改? -
遍历从
i=1开始,第一天只更新minPrice而不计算利润。如果数组长度为 1,代码能正确返回0吗? -
如果股票价格可能为 负数(虽然题目保证非负),贪心策略是否仍然有效?为什么?
📚 延伸挑战
-
如果不仅要返回最大利润,还要返回 具体的买入和卖出日期,你如何在贪心过程中记录下标?
-
对比本题与 最大子数组和(LeetCode 53)的相似之处:两者都是求最大差值(前者是价格差,后者是连续和),你能用最大子数组和的思想重新实现本题吗?(提示:将价格转换为每日涨跌幅)
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨
 —— 题解&spm=1001.2101.3001.5002&articleId=163151067&d=1&t=3&u=6f05f74cc8a045f0aa3f3c3a096d6b11)
1080

被折叠的 条评论
为什么被折叠?



