news 2026/2/13 6:46:58

从一道面试题看算法思维:最小栈(Min Stack)的从 O(N) 到 O(1) 进化之路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从一道面试题看算法思维:最小栈(Min Stack)的从 O(N) 到 O(1) 进化之路

“最小栈”(LeetCode 155题)作为一道非常经典的试金石。它不涉及复杂的动态规划或图论,却能精准地考察候选人对数据结构的理解深度以及空间换时间这一核心算法思想的掌握程度。
题目要求看似简单:设计一个栈,支持 push、pop、top 操作,并能在常数时间

O(1)

内检索到最小元素 getMin。
很多候选人在看到题目时,往往忽略了

O(1)

这个关键约束,直接给出了一个功能正确但性能不达标的方案。今天,我们就从面试官的视角,来剖析这道题从

O(N)

O(1)

的进化过程。
第一阶段:直觉与妥协 (暴力解法)
当被问到“如何实现一个支持获取最小值的栈”时,大多数人的第一直觉是利用 JavaScript 数组的原生方法。既然栈的本质是先进后出(LIFO),那么 push、pop 和 top 都很容易实现。
对于 getMin,最朴素的想法是:既然我要找最小值,那就遍历整个数组好了。
以下是这类回答的典型代码实现:
JavaScript

// ES5 构造函数 const MiniStack = function() { this.stack = []; // 存储数据的数组 } MiniStack.prototype.push = function(x) { this.stack.push(x); } MiniStack.prototype.pop = function() { return this.stack.pop(); } MiniStack.prototype.top = function() { if(!this.stack || !this.stack.length){ return; } return this.stack[this.stack.length-1]; } // 暴力解法核心:遍历查找 O(N) MiniStack.prototype.getMin = function() { let minValue = Infinity; // 初始化为无穷大 const { stack } = this; // 遍历整个栈寻找最小值 for(let i = 0; i < stack.length; i++){ if(stack[i] < minValue){ minValue = stack[i]; } } return minValue; }

面试官点评
这份代码从功能上来说是正确的。它利用了 JS 数组模拟栈,push、pop、top 的时间复杂度确实是

O(1)


但是,getMin 方法的实现存在致命弱点:它的时间复杂度是

O(N)


随着栈内元素数量(N)的增加,获取最小值的耗时将线性增长。如果在高频调用的场景下,这种遍历操作是不可接受的性能瓶颈。在面试中,这只能算是一个“勉强及格”的答案,因为它没有体现出任何算法优化的思维。
第二阶段:思维跃迁 (空间换时间)
如何将

O(N)

优化为

O(1)

?这需要我们转换思维。
在暴力解法中,我们每次调用 getMin 都要重新计算。也就是我们“忘记”了之前的比较结果。如果我们能有一种机制,能够“记住”随着数据入栈过程中,每一个状态下的最小值,那就不需要回头遍历了。
这里引入算法设计中极重要的思想:空间换时间
我们需要引入一个辅助栈(Auxiliary Stack)

  • 数据栈 (stack):负责常规的数据存储,维持栈的正常逻辑。
  • 辅助栈 (stack2):负责同步存储当前数据栈对应的最小值。

核心策略:辅助栈的栈顶,永远存储着当前数据栈中所有元素的最小值。这实际上维护了一个非严格单调递减的序列。
第三阶段:完美实现 (双栈协同)
有了辅助栈的思路,接下来的难点在于:如何保持两个栈的状态同步?
我们需要处理好两个关键逻辑:

  1. 入栈 (Push):新元素进来了,辅助栈存不存?
  2. 出栈 (Pop):数据栈弹出了,辅助栈要不要跟着弹?

以下是经过逻辑修正后的

O(1)

完美实现:
JavaScript

const MiniStack = function() { this.stack = []; // 数据栈 this.stack2 = []; // 辅助栈(单调栈,栈顶即为最小值) } // O(1) MiniStack.prototype.push = function(x) { // 1. 数据栈必须入栈 this.stack.push(x); // 2. 辅助栈入栈逻辑 // 如果辅助栈为空,或者新元素 x 小于等于 辅助栈栈顶,则入辅助栈 // 注意:这里必须是 <=,不能只是 <,否则会有重复最小值丢失的问题 if(this.stack2.length === 0 || x <= this.stack2[this.stack2.length-1]) { this.stack2.push(x); } } // O(1) MiniStack.prototype.pop = function() { // 1. 数据栈弹出 const val = this.stack.pop(); // 2. 辅助栈同步逻辑 // 如果弹出的元素等于辅助栈栈顶元素,说明最小值被移除,辅助栈也要弹出 if(val === this.stack2[this.stack2.length-1]) { this.stack2.pop(); } return val; } // O(1) MiniStack.prototype.top = function() { return this.stack[this.stack.length-1]; } // O(1) MiniStack.prototype.getMin = function() { // 直接返回辅助栈栈顶,无需遍历 return this.stack2[this.stack2.length-1]; }

深度解析
1. Push 操作的去重与同步
在 push 方法中,判断条件 x <= this.stack2[top] 至关重要。

  • 为什么要判断大小?我们只关心比当前最小值更小(或相等)的数。如果新来的数比当前最小值还大,它绝不可能是当前的最小值,因此不需要压入辅助栈。这保证了辅助栈的单调递减特性。
  • 为什么要包含等于(<=)?这是一个常见的坑。假设入栈序列为 [5, 2, 2]。
    • 如果不包含等于:辅助栈通过判断只存入第一个 2。
    • 当数据栈弹出最上面的 2 时,代码会误以为最小值被移除了,导致辅助栈唯一的 2 也被弹出。
    • 此时数据栈里还剩一个 2,但辅助栈里的最小值变成了 5。这就产生了 Bug。
    • 因此,重复的最小值必须同时压入辅助栈

2. Pop 操作的同步
在 pop 方法中,我们对比数据栈弹出的值与辅助栈栈顶的值。
只有当两者相等时,才弹出辅助栈。这意味着:我们移除的正是当前的最小值。辅助栈弹出后,新的栈顶自然就变成了“次小值”(即之前的最小值),完美还原了历史状态。
3. GetMin 的极致性能
由于辅助栈的精心维护,其栈顶永远是全局最小值。getMin 变成了简单的数组索引访问,没有任何循环,时间复杂度稳稳落在 O(1)
总结:
辅助栈就像是数据栈的“历史快照”索引。无论数据栈怎么进出,辅助栈的栈顶始终指向“当前存活数据中的最小者”。这种设计将 getMin 的复杂度从线性阶降维到了常数阶。在实际面试中,写出代码往往只占 60% 的分数。剩下的 40% 取决于你能否清晰地解释“为什么引入辅助栈”、“如何处理重复最小值”以及“空间与时间的权衡”。算法不仅仅是背诵代码,更是对数据流动和资源消耗的精准控制。
如果这篇文章对你有帮助的话,就请你点个赞吧!!!

原文: https://juejin.cn/post/75995852
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/5 6:13:53

mapbox进阶,使用geoserver矢量切片图层组服务(pbf)加载图层

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言 1.1 ☘️mapboxgl.Map 地图对象 1.2 ☘️mapboxgl.Map style属性 1.3 ☘️line线图层样式 1.4 ☘️Fill面图层…

作者头像 李华
网站建设 2026/2/12 23:36:03

实测拆解:Qwen3-Max-Thinking 到底能不能对标 GPT-5.2?

最近刷到通义千问刚发布的旗舰推理模型 Qwen3-Max-Thinking&#xff0c;看完它的测试报告我直接坐不住了 —— 这性能已经能对标 GPT-5.2、Claude Opus 4.5 这些顶流模型了&#xff1f;今天就带大家拆解这份测试报告&#xff0c;用大白话讲清楚它到底有多能打。 一、先搞懂&…

作者头像 李华
网站建设 2026/2/12 19:52:17

为什么企业应制定全面的服务器DDoS防护策略?

服务器DDoS防护策略的重要性DDoS攻击通过大量虚假流量淹没目标服务器&#xff0c;导致服务中断或资源耗尽。企业制定全面的防护策略可避免业务损失、数据泄露及声誉受损。业务连续性的保障DDoS攻击可能导致关键服务瘫痪&#xff0c;直接影响客户体验和收入。防护策略通过实时流…

作者头像 李华
网站建设 2026/2/13 3:55:22

Debian 9 (Stretch)仓库无法使用

背景&#xff1a;用镜像拉起的mysql5.6.44的容器&#xff0c;os是Debian GNU/Linux 9 (stretch) 这个错误表明系统仍在使用 Debian 9 (Stretch)&#xff0c;但该版本已于 2022年6月30日 结束生命周期&#xff08;EOL&#xff09;&#xff0c;官方仓库已下线。 解决方案 # 进入…

作者头像 李华
网站建设 2026/2/6 23:51:21

得物商品详情API接口在数据分析中的应用

得物商品详情 API 接口在数据分析领域的应用&#xff0c;核心是获取标准化的商品核心数据&#xff0c;并围绕电商业务场景&#xff08;选品、竞品分析、价格监控、用户洞察等&#xff09;构建数据驱动的决策体系。结合得物平台以潮流服饰、球鞋、美妆、奢品为主的品类特性&…

作者头像 李华
网站建设 2026/2/8 14:23:00

AI写论文哪个软件最好?实测5款工具后,虎贲等考AI凭硬实力封神

毕业季来临&#xff0c;“AI写论文哪个软件最好”成了无数学子的灵魂拷问。面对市面上五花八门的AI写作工具&#xff0c;有人踩坑“生成内容逻辑断层”&#xff0c;有人栽在“查重率飙至40%”&#xff0c;还有人被“虚构文献无法追溯”逼到返工。为了帮大家精准避坑&#xff0c…

作者头像 李华