当前位置: 首页 > ops >正文

[从零开始面试算法] (12/100) LeetCode 121. 买卖股票:一次遍历的“后悔药”

引言

欢迎来到本系列的第十二篇!今天,我们将首次涉足一个非常庞大且经典的面试题系列——股票买卖问题。我们将从这个系列中最简单、最基础的一道题 LeetCode 121. 买卖股票的最佳时机 开始。

这道题表面上看似简单,但它背后蕴含的贪心思想动态规划的思维方式,是解决更复杂股票问题的基石。

很多同学(包括我!)在初次接触这道题时,可能会陷入一种复杂的思维模式:“我是不是要先找到一个最低点,然后再找它后面的最高点?如果后面有更低的最低点怎么办?” 这种思路虽然符合直觉,但实现起来却容易变得复杂。

本文将带你一起:

  1. 分析这种“寻找极值点”的直觉思路。

  2. 提炼并掌握一个极其简洁的一次遍历解法,我称之为“后悔药”模型。

  3. 通过代码,深刻理解如何通过维护“历史最低价”来动态求解“最大利润”。


一、题目呈现

  • 题目编号:121. 买卖股票的最佳时机

  • 题目难度:简单

  • 题目链接:121. 买卖股票的最佳时机 - 力扣 (LeetCode)

  • 题目描述

    给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

    你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

    返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,则返回 0 。

  • 示例

    • 输入: [7,1,5,3,6,4] -> 输出: 5 (解释: 在第 2 天(价格 = 1)买入,在第 5 天(价格 = 6)卖出,最大利润 = 6-1 = 5。)

    • 输入: [7,6,4,3,1] -> 输出: 0 (解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。)

二、我的思考:复杂的“寻找极值点”思路

这里完全采用你的笔记思路

看到题目要求“最大利润”,也就是价格差最大,我的第一反应是:

  1. 先遍历数组,找到一个价格最低点(“极小值”),把它当作我的买入点。

  2. 然后,在这个买入点之后,再遍历一遍,找到一个价格最高点,计算利润。

但这里有一个问题:如果在这个最低点后面,又出现了一个更低的最低点呢?那我的最佳买入时机就可能改变了。

所以,我的思路变得更复杂了:我需要不断地寻找局部最低点,然后计算从这个最低点出发能获得的最大利润。最后,在所有这些“局部最大利润”中,再找到那个全局最大的。

这个思路是正确的,它比 O(n²) 的纯暴力(遍历所有买入卖出组合)要高效。但它的逻辑分支很多,需要处理各种“分段”和“比较”,代码实现起来容易出错且不够简洁。

那么,有没有一种更简单、更统一的思维模型呢?


三、豁然开朗:一次遍历的“后悔药”模型

让我们换一个角度思考。我们不需要瞻前顾后,只需要遍历一次,在每一天都做出“当下”的最优决策。

想象一下,你正坐着时光机,一天一天地穿越价格历史。在每一天 i,你都可以问自己一个问题:

“如果我必须在今天(第 i 天)卖出股票,那么我能获得的最大利润是多少?”

答案很显然:要想今天卖出赚得最多,我当初的买入价必须是在今天(第 i 天)之前的所有日子里,价格最低的那一天

这个思想给了我们一剂“后悔药”:我永远可以假定自己是在历史最低点买入的。

算法流程诞生了:

  1. 我们只需要维护两个变量:

    • min_price:记录到今天为止,我所见过的历史最低价

    • max_profit:记录到今天为止,我所能获得的最高利润

  2. 我们从第一天开始遍历到最后一天。在每一天 price:

    • 第一步:计算潜在利润。我用今天的价格 price 减去我之前记录的 min_price,看看如果今天卖出能赚多少。然后,我用这个“潜在利润”去更新我的 max_profit,取两者中较大的那个。

    • 第二步:更新“后悔药”。我再看看今天的价格 price 是不是比我之前记录的 min_price 还要低。如果是,我就更新 min_price。这样,我就为未来的日子准备好了新的“历史最低买入价”。

  3. 遍历结束后,max_profit 里存储的就是全局的最大利润。


四、C++ 最优代码与详解

这个“一次遍历”的思路,可以被翻译成极其简洁的代码。

完整代码
#include <vector>
#include <algorithm> // 为了使用 std::min 和 std::max
#include <climits>   // 为了使用 INT_MAXclass Solution {
public:int maxProfit(std::vector<int>& prices) {// 1.int min_price = INT_MAX;int max_profit = 0;// 2.for (int price : prices) {// 3.max_profit = std::max(max_profit, price - min_price);// 4.min_price = std::min(min_price, price);}// 5.return max_profit;}
};

代码逐点解释

  1. int min_price = INT_MAX; int max_profit = 0;
    初始化历史最低价为一个不可能达到的最大值,确保第一个价格能成为初始的最低价。初始化最大利润为 0,因为题目规定如果无利润则返回 0。

  2. for (int price : prices)
    遍历每一天的价格 price。

  3. max_profit = std::max(max_profit, price - min_price);
    更新最大利润。计算“今天卖出的潜在利润”(price - min_price),并和之前记录的 max_profit 比较,取更大的那个。注意:在第一次循环时,min_price是INT_MAX,所以 price - min_price 是一个负数,max_profit 仍然是 0,这是正确的。

  4. min_price = std::min(min_price, price);
    更新“后悔药”。在今天的价格 price 和之前的 min_price 之间,选择更小的那个作为新的历史最低价,为明天的决策做准备。

  5. return max_profit;
    遍历结束后,max_profit 就是全局的最大利润。

重要细节:为什么是先更新 max_profit 再更新 min_price?因为题目要求必须在未来的某一天卖出。如果在同一天买入和卖出,利润为 0。我们的代码顺序保证了,在计算当天利润时,使用的 min_price 一定是当天或当天之前的最低价,完美地符合了“先买后卖”的规则。


五、总结与收获

  • 复杂度分析:我们只对价格数组进行了一次遍历,因此时间复杂度为 O(n)。我们只使用了两个额外的变量,空间复杂度为 O(1)。这已经是这道题的最优解。

  • 核心思想:这道题是贪心算法动态规划的一个极简应用。其核心在于,我们不需要考虑所有复杂的买卖组合,只需要在遍历过程中,动态地维护“到目前为止的最低价格”和“到目前为止的最大利润”这两个状态即可。

  • 关键技巧:“先计算当天可能的最大利润,再更新当天的价格为可能的历史最低价”这个顺序,巧妙地处理了“先买后卖”的时间限制。

http://www.xdnf.cn/news/20272.html

相关文章:

  • 高维前缀和
  • Android系统更新系统webview. 2025-09-06
  • gcloud cli 使用 impersonate模拟 服务帐号
  • 2025年财会专业人士职业发展认证路径分析
  • 从“帮写文案”到“管生活”:个人AI工具的边界在哪?
  • Transformer架构(详解)
  • 记一次:mysql的json及json数组使用组合使用
  • 【基础-单选】关于UIAbility的启动模式,下列说法错误的是:
  • Redis 事务与 Lua 脚本:原子操作实战指南
  • LeetCode 2461.长度为K子数组中的最大和
  • 【FastDDS】 Entity Policy 之 标准Qos策略
  • OpenHarmony之USB Manager 架构深度解析
  • 【视网膜分割】AFMIP-Net:一种新型的自适应特征调制和隐式提示网络
  • AI、人工智能础: 实体命名!
  • 郭平《常变与长青》读书笔记(第一章)
  • QT之实现点击按钮启动另一个桌面应用程序
  • 【开题答辩全过程】以 停车场管理系统的设计与实现为例,包含答辩的问题和答案
  • 点晴模切ERP与MES系统整合:模切工厂数字化转型关键
  • 内网后渗透攻击--linux系统(横向移动)
  • Python趣味入门:打印与计算初体验
  • 垃圾收集器分类
  • 「数据获取」《中国电力统计年鉴》(1993-2024)(含中国电力年鉴)
  • 分布式数据库的历史演变与核心原理
  • SpringBoot配置文件
  • 【CSP-S】数据结构 ST 表详解
  • 植物大战僵尸融合版安装包,下载安装教程
  • PCDN工作原理的详细步骤
  • Netty从0到1系列之EventLoopGroup
  • Kafka面试精讲 Day 10:事务机制与幂等性保证
  • CUDA默认流的同步行为