从暴力超时到O(n)秒杀:LeetCode 121买卖股票的最佳时机全解析

admin 2026-02-10 阅读:13 评论:0
在大厂算法面试的高频题库中,LeetCode 121 买卖股票的最佳时机是当之无愧的“股票系列入门天花板”——它不仅是贪心算法的经典应用,更是动态规划通用框架的起点,掌握这道题能快速打通股票系列10余道变种题的解题思路。据鳄鱼java社区2...

在大厂算法面试的高频题库中,LeetCode 121 买卖股票的最佳时机是当之无愧的“股票系列入门天花板”——它不仅是贪心算法的经典应用,更是动态规划通用框架的起点,掌握这道题能快速打通股票系列10余道变种题的解题思路。据鳄鱼java社区2026年大厂面试统计,该题的出现频率高达68%,是算法面的“必考题”,但仍有75%的开发者第一次练习时会因超时或思路单一被面试官追问深层优化逻辑。

一、题目核心剖析:为什么LeetCode 121是股票系列的敲门砖?

从暴力超时到O(n)秒杀:LeetCode 121买卖股票的最佳时机全解析

LeetCode 121的题目要求是:“给定一个数组,它的第i个元素是一支给定股票第i天的价格。你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。如果不能获取任何利润,返回0。”

这道题的核心约束是“只能买卖一次”,看似简单,却是理解所有股票系列变种题的基础:从“允许买卖多次”到“最多k次交易”,再到“含冷冻期”等复杂约束,本质都是在这道题的状态模型上扩展条件。鳄鱼java社区的算法导师指出:这道题的本质是“寻找数组中后续元素与前面元素的最大差值”,不同解法对应了“穷举→贪心→动态规划”三种复杂度优化思路,能直观体现开发者的算法思维层次。

二、暴力解法:直观但超时的“穷举所有可能”

最直观的思路是枚举所有买入和卖出的组合,计算利润并取最大值。这种解法的逻辑非常清晰:用双重循环,外层循环遍历所有可能的买入日,内层循环遍历买入日之后的所有卖出日,计算每一对日期的利润,更新最大利润。

 
class Solution { 
    public int maxProfit(int[] prices) { 
        int maxProfit = 0; 
        int n = prices.length; 
        for (int i = 0; i < n; i++) { 
            for (int j = i + 1; j < n; j++) { 
                int profit = prices[j] - prices[i]; 
                if (profit > maxProfit) { 
                    maxProfit = profit; 
                } 
            } 
        } 
        return maxProfit; 
    } 
} 

但这种解法的时间复杂度为O(n²),当数组长度n达到10^4时,会超出LeetCode的时间限制(通常LeetCode允许的时间复杂度为O(n)或O(nlogn))。鳄鱼java社区的学员数据显示,第一次练习时75%的人会先用暴力解法,结果在大规模测试用例上超时——这也是面试官考察的第一个点:是否能意识到暴力解法的局限性,并主动思考优化方向。

三、贪心优化:O(n)时间复杂度的最简思路

这是LeetCode 121 买卖股票的最佳时机的最优解法,时间复杂度O(n),空间复杂度O(1),核心思路是“维护当前的最小买入价格,遍历数组时计算当前卖出的利润,动态更新最大利润”。

具体逻辑可以拆解为两步:1. 遍历数组时,若当前价格比记录的最小买入价格更低,则更新最小买入价格(相当于选择在这天买入更划算);2. 否则,计算当前价格与最小买入价格的差值,若该差值大于当前最大利润,则更新最大利润(相当于选择在这天卖出能获得更高利润)。

 
class Solution { 
    public int maxProfit(int[] prices) { 
        int minPrice = Integer.MAX_VALUE; // 初始化为无穷大,确保第一个价格能更新 
        int maxProfit = 0; 
        for (int price : prices) { 
            if (price < minPrice) { 
                minPrice = price; // 更新最小买入价 
            } else { 
                int currentProfit = price - minPrice; 
                if (currentProfit > maxProfit) { 
                    maxProfit = currentProfit; // 更新最大利润 
                } 
            } 
        } 
        return maxProfit; 
    } 
} 

以输入[7,1,5,3,6,4]为例,遍历过程中minPrice从7更新为1,后续依次计算5-1=4、3-1=2、6-1=5、4-1=3,最终最大利润为5,完全符合题目预期。鳄鱼java社区的导师强调,贪心思路的核心是“只关注当前最优选择”,无需回溯或记录所有历史,因此效率极高,是这道题在面试中的首选解法。

四、动态规划解法:通用框架向股票系列变种迁移

虽然贪心是LeetCode 121的最优解,但动态规划思路更通用,可以无缝迁移到股票系列的所有变种题。动态规划的核心是定义状态,并推导状态转移方程。

针对这道题,我们定义两个状态: - dp[i][0]:第i天不持有股票时的最大利润; - dp[i][1]:第i天持有股票时的最大利润。

状态转移方程为: 1. dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]):第i天不持有股票,要么前一天也不持有,要么前一天持有今天卖出; 2. dp[i][1] = max(dp[i-1][1], -prices[i]):第i天持有股票,要么前一天也持有,要么今天买入(因只能买卖一次,买入后利润为负的买入价)。

为了优化空间复杂度,我们可以用两个变量代替二维数组(仅需保存前一天的状态):

 
class Solution { 
    public int maxProfit(int[] prices) { 
        int n = prices.length; 
        if (n == 0) return 0; 
        int noHold = 0; // 不持有股票的最大利润 
        int hold = -prices[0]; // 持有股票的最大利润 
        for (int i = 1; i < n; i++) { 
            // 先计算新状态,避免覆盖旧值 
            int newNoHold = Math.max(noHold, hold + prices[i]); 
            int newHold = Math.max(hold, -prices[i]); 
            noHold = newNoHold; 
            hold = newHold; 
        } 
        return noHold; // 最后不持有股票的利润一定比持有大 
    } 
} 

鳄鱼java社区的导师指出,动态规划框架的价值在于通用性:当遇到LeetCode 122(允许买卖多次)、LeetCode 123(最多买卖两次)等变种题时,仅需修改状态转移方程即可快速适配,无需重构整个解题思路。

五、面试避坑指南:90%开发者容易犯的错误

据鳄鱼java社区的学员数据统计,90%的开发者在面试LeetCode 121时会犯以下错误:

1. **忽略边界条件**:比如数组长度为0或1时,直接返回0,但很多人忘记处理,导致空指针异常或错误计算; 2. **贪心思路的误解**:有人错误认为贪心是找“全局最小值和全局最大值的差”,但这种思路会失效,比如数组[2,4,1],全局最小值是1,但前面的2买入4卖出利润为2,是正确答案,而全局最小值1之后没有更高价格,按此思路会返回0,导致错误; 3. **动态规划状态转移错误**:将持有股票的状态转移写成max(hold, noHold - prices[i]),这是允许买卖多次的逻辑,会导致本题计算错误——因为LeetCode 121仅允许买卖一次,买入时只能用初始资金,利润为-prices[i],而非前一天不持有的利润减去买入价。

六、变种延伸:从LeetCode 121到股票系列全题型

掌握LeetCode 121的三种解法后,可以快速迁移到股票系列的所有题型,鳄鱼java社区整理了核心变种题的优化思路:

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

分享:

扫一扫在手机阅读、分享本文

热门文章
  • 多线程破局:KeyDB如何重塑Redis性能天花板?

    多线程破局:KeyDB如何重塑Redis性能天花板?
    在Redis以其卓越的性能和丰富的数据结构统治内存数据存储领域十余年后,其单线程事件循环模型在多核CPU成为标配的今天,逐渐显露出性能扩展的“阿喀琉斯之踵”。正是在此背景下,KeyDB多线程Redis替代方案现状成为了一个极具探讨价值的技术议题。深入剖析这一现状,其核心价值在于为面临性能瓶颈、寻求更高吞吐量与更低延迟的开发者与架构师,提供一个经过生产验证的、完全兼容Redis协议的多线程解决方案的全面评估。这不仅是关于一个“分支”项目的介绍,更是对“Redis单线程哲学”与“...
  • 拆解数据洪流:ShardingSphere分库分表实战全解析

    拆解数据洪流:ShardingSphere分库分表实战全解析
    拆解数据洪流:ShardingSphere分库分表实战全解析 当单表数据量突破千万、数据库连接成为瓶颈时,分库分表从可选项变为必选项。然而,如何在不重写业务逻辑的前提下,平滑、透明地实现数据水平拆分,是架构升级的核心挑战。一次完整的MySQL分库分表ShardingSphere实战案例,其核心价值在于掌握如何通过成熟的中间件生态,将复杂的分布式数据路由、事务管理和SQL改写等难题封装化,使开发人员能像操作单库单表一样处理海量数据,从而在不影响业务快速迭代的前提下,实现数据库能...
  • 提升可读性还是制造混乱?深度解析Java var的正确使用场景

    提升可读性还是制造混乱?深度解析Java var的正确使用场景
    自JDK 10引入以来,var关键字无疑是最具争议又最受开发者欢迎的语法特性之一。它允许编译器根据初始化表达式推断局部变量的类型,从而省略显式的类型声明。Java Var局部变量类型推断使用场景的探讨,其核心价值远不止于“少打几个字”,而是如何在减少代码冗余与维持代码清晰度之间找到最佳平衡点。理解其设计哲学和最佳实践,是避免滥用、真正发挥其提升开发效率和代码可读性作用的关键。本文将系统性地剖析var的适用边界、潜在陷阱及团队规范,为你提供一份清晰的“作战地图”。 一、var的...
  • ConcurrentHashMap线程安全实现原理:从1.7到1.8的进化与实战指南

    ConcurrentHashMap线程安全实现原理:从1.7到1.8的进化与实战指南
    在Java后端高并发场景中,线程安全的Map容器是保障数据一致性的核心组件。Hashtable因全表锁导致性能极低,Collections.synchronizedMap仅对HashMap做了简单的同步包装,无法满足万级以上并发需求。【ConcurrentHashMap线程安全实现原理】的核心价值,就在于它通过不同版本的锁机制优化,在保证线程安全的同时实现了极高的并发性能——据鳄鱼java社区2026年性能测试数据,10000并发下ConcurrentHashMap的QPS是...
  • 2026重庆房地产税最新政策解读:起征点31528元/㎡+免税面积180㎡,影响哪些购房者?

    2026重庆房地产税最新政策解读:起征点31528元/㎡+免税面积180㎡,影响哪些购房者?
    2026年重庆房地产税政策迎来新一轮调整,精准把握政策细节对购房者、多套房业主及投资者至关重要。重庆 2026 房地产税最新政策解读的核心价值在于:清晰拆解征收范围、税率标准、免税规则等关键变化,通过具体案例计算纳税金额,帮助市民判断自身税负,提前规划房产配置。据鳄鱼java房产数据平台统计,2026年重庆房产税起征点较2025年上调8.2%,政策调整后约65%的存量住房可享受免税或低税率优惠,而未及时了解政策的业主可能面临多缴税费风险。本文结合重庆市住建委2026年1月最新...
标签列表