1 题目
提示
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack()初始化堆栈对象。void push(int val)将元素val推入堆栈。void pop()删除堆栈顶部的元素。int top()获取堆栈顶部的元素。int getMin()获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
-231 <= val <= 231 - 1pop、top和getMin操作总是在 非空栈 上调用push,pop,top, andgetMin最多被调用3 * 104次
2 代码实现
c++
cpp
class MinStack {
private:
stack<int> mainStack ;
stack<int> minStack ;
public:
MinStack() {
}
void push(int val) {
mainStack.push(val);
if(minStack.empty() || val <= minStack.top()){
minStack.push(val);
}
}
void pop() {
if(mainStack.top() == minStack.top()){
minStack.pop();
}
mainStack.pop();
}
int top() {
return mainStack.top();
}
int getMin() {
return minStack.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
js
javascript
var MinStack = function() {
this.minStk = [];
this.mainStk = [];
};
/**
* @param {number} val
* @return {void}
*/
MinStack.prototype.push = function(val) {
this.mainStk.push(val);
if(this.minStk.length === 0 || this.minStk[this.minStk.length - 1 ] >= val ){
this.minStk.push(val);
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function() {
if (this.mainStk[this.mainStk.length - 1 ] === this.minStk[this.minStk.length - 1 ]){
this.minStk.pop();
}
this.mainStk.pop();
};
/**
* @return {number}
*/
MinStack.prototype.top = function() {
return this.mainStk[this.mainStk.length - 1 ];
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
return this.minStk[this.minStk.length - 1 ];
};
/**
* Your MinStack object will be instantiated and called as such:
* var obj = new MinStack()
* obj.push(val)
* obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.getMin()
*/
思考
毫无头绪,怎么叫我写这些push的内置方法,难道吧这些基础的方法内置一下取最小的逻辑?实现"看人下菜"?
毫无头绪啊...一个字蹦不出来。
题解
1. 问题分析与解题思路
要实现一个能在常数时间 O (1) 内获取最小值的栈,核心难点在于如何高效维护最小值。如果每次调用 getMin() 都遍历栈找最小值,时间复杂度会是 O (n),不符合要求。
核心思路:辅助栈
我们用两个栈来实现:
- 主栈 :存储所有入栈的元素,完成
push、pop、top基础操作。 - 辅助栈(最小栈) :同步存储当前主栈中的最小值,确保栈顶始终是当前主栈的最小值。
push时:若新元素 ≤ 辅助栈栈顶(或辅助栈为空),则同时压入辅助栈;否则仅压入主栈。pop时:若主栈栈顶等于辅助栈栈顶,说明弹出的是当前最小值,需同时弹出辅助栈栈顶;否则仅弹主栈。getMin时:直接返回辅助栈栈顶即可(O (1) 时间)。
2. 完整 C++ 代码实现
cpp
#include <iostream>
#include <stack>
using namespace std;
class MinStack {
private:
// 主栈:存储所有元素
stack<int> mainStack;
// 辅助栈:存储当前最小值,栈顶始终是主栈的最小值
stack<int> minStack;
public:
// 构造函数:初始化两个空栈(C++中stack默认构造即为空,可省略)
MinStack() {
}
// 入栈操作
void push(int val) {
// 1. 先将元素压入主栈(必做)
mainStack.push(val);
// 2. 辅助栈处理:空栈 或 新元素≤辅助栈顶(当前最小值),则压入辅助栈
if (minStack.empty() || val <= minStack.top()) {
minStack.push(val);
}
}
// 出栈操作
void pop() {
// 1. 先判断主栈顶是否等于辅助栈顶(即弹出的是当前最小值)
if (mainStack.top() == minStack.top()) {
minStack.pop(); // 辅助栈同步弹出
}
// 2. 主栈弹出(必做)
mainStack.pop();
}
// 获取栈顶元素(主栈顶)
int top() {
return mainStack.top();
}
// 获取最小值(辅助栈顶)
int getMin() {
return minStack.top();
}
};
// 测试用例(可直接运行)
int main() {
MinStack minStack;
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
cout << minStack.getMin() << endl; // 输出 -3
minStack.pop();
cout << minStack.top() << endl; // 输出 0
cout << minStack.getMin() << endl; // 输出 -2
return 0;
}
3. 代码逐行解释
(1)成员变量定义
cpp
stack<int> mainStack; // 主栈:存储所有元素
stack<int> minStack; // 辅助栈:存储最小值
- C++ 的
stack是 STL 容器,默认提供push()、pop()、top()、empty()方法,无需自己实现基础栈功能。
(2)构造函数 MinStack()
cpp
MinStack() {}
- C++ 中栈对象默认构造就是空栈,因此构造函数无需额外逻辑,空实现即可。
(3)入栈 push(int val)
cpp
void push(int val) {
mainStack.push(val); // 主栈必压入
if (minStack.empty() || val <= minStack.top()) {
minStack.push(val); // 辅助栈仅在"空"或"新元素更小/相等"时压入
}
}
- 关键逻辑:
val <= minStack.top()用 ≤ 而非 <,是为了处理重复最小值(比如连续压入多个 - 3,弹出时能正确同步)。
(4)出栈 pop()
cpp
void pop() {
if (mainStack.top() == minStack.top()) {
minStack.pop(); // 弹出的是最小值,辅助栈同步弹出
}
mainStack.pop(); // 主栈必弹出
}
- 注意:题目保证
pop在非空栈调用,因此无需判断栈空。
(5)获取栈顶 top()
cpp
int top() {
return mainStack.top();
}
- 直接返回主栈栈顶,符合普通栈的
top逻辑。
(6)获取最小值 getMin()
cpp
int getMin() {
return minStack.top();
}
- 辅助栈顶始终是当前主栈的最小值,因此直接返回即可,时间复杂度 O (1)。
4. 测试用例运行过程
以题目示例为例,逐步拆解:
| 操作 | 主栈内容 | 辅助栈内容 | 说明 |
|---|---|---|---|
| push(-2) | [-2] | [-2] | 辅助栈空,压入 - 2 |
| push(0) | [-2, 0] | [-2] | 0 > -2,辅助栈不压入 |
| push(-3) | [-2, 0, -3] | [-2, -3] | -3 ≤ -2,辅助栈压入 - 3 |
| getMin() | - | - | 返回辅助栈顶 -3 |
| pop() | [-2, 0] | [-2] | 主栈顶 - 3 = 辅助栈顶 - 3,辅助栈弹出 |
| top() | - | - | 返回主栈顶 0 |
| getMin() | - | - | 返回辅助栈顶 -2 |
总结
- 核心方案 :用「主栈 + 辅助栈」实现,辅助栈始终存储当前主栈的最小值,保证
getMin()是 O (1) 时间。 - 关键逻辑 :
push时辅助栈仅压入更小 / 相等的元素,pop时仅当弹出的是最小值才同步弹出辅助栈。 - 复杂度:所有操作(push/pop/top/getMin)均为 O (1) 时间,空间复杂度 O (n)(最坏情况辅助栈和主栈元素相同)。
这个实现完全满足题目要求,且符合 C++ 代码规范,可直接提交 LeetCode 155 题(最小栈)通过所有测试用例。
js版本为何又错了
javascript
var MinStack = function() {
const minStk = [];
const mainStk = [];
};
/**
* @param {number} val
* @return {void}
*/
MinStack.prototype.push = function(val) {
mainStk.push(val);
if(minStk.length === 0 || minStk[minStk.length - 1 ] > val ){
minStk.push(val);
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function() {
if (mainStk[mainStk.length - 1 ] === minStk[minStk.length - 1 ]){
minStk.pop();
}
mainStk.pop();
};
/**
* @return {number}
*/
MinStack.prototype.top = function() {
return mainStk.top();
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
return minStk.top();
};
/**
* Your MinStack object will be instantiated and called as such:
* var obj = new MinStack()
* obj.push(val)
* obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.getMin()
*/
1. 代码问题分析与修复
你这段 JavaScript 代码的核心思路(主栈 + 辅助栈)是对的,但存在多个语法错误和逻辑漏洞,导致无法正常运行。我会先指出问题,再给出完整的修复版本。
问题总结
| 问题位置 | 具体问题 |
|---|---|
构造函数 MinStack() |
用 const 声明的数组是局部变量,原型方法无法访问(应改为实例属性 this.xxx) |
push() 逻辑 |
判断条件用 > 会漏掉相等的最小值 (应改为 >=) |
top()/getMin() |
JavaScript 数组没有 top() 方法,需用 [length-1] 获取最后一个元素 |
2. 修复后的完整代码
javascript
var MinStack = function() {
// 改为实例属性(this.),让原型方法能访问
this.mainStk = []; // 主栈:存储所有元素
this.minStk = []; // 辅助栈:存储当前最小值
};
/**
* @param {number} val
* @return {void}
*/
MinStack.prototype.push = function(val) {
// 1. 主栈必压入元素
this.mainStk.push(val);
// 2. 辅助栈逻辑:空栈 或 新元素≤当前最小值(用<=避免漏相等值)
if (this.minStk.length === 0 || val <= this.minStk[this.minStk.length - 1]) {
this.minStk.push(val);
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function() {
// 1. 取出主栈栈顶元素(避免重复计算 this.mainStk[this.mainStk.length - 1])
const topVal = this.mainStk.pop();
// 2. 如果弹出的是当前最小值,辅助栈同步弹出
if (topVal === this.minStk[this.minStk.length - 1]) {
this.minStk.pop();
}
};
/**
* @return {number}
*/
MinStack.prototype.top = function() {
// JavaScript 数组用 [length-1] 获取最后一个元素(栈顶)
return this.mainStk[this.mainStk.length - 1];
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
// 辅助栈顶就是当前最小值
return this.minStk[this.minStk.length - 1];
};
// 测试用例(可直接运行)
var obj = new MinStack();
obj.push(-2);
obj.push(0);
obj.push(-3);
console.log(obj.getMin()); // 输出 -3
obj.pop();
console.log(obj.top()); // 输出 0
console.log(obj.getMin()); // 输出 -2
3. 关键修改点解释
(1)构造函数:局部变量 → 实例属性
javascript
// 错误写法(const 声明的是局部变量,原型方法访问不到)
const minStk = [];
const mainStk = [];
// 正确写法(this 绑定到实例,所有原型方法都能访问)
this.minStk = [];
this.mainStk = [];
- JavaScript 中,构造函数里用
const/let/var声明的变量是局部变量 ,原型方法(如push/pop)无法访问;只有挂载到this上的属性,才是实例的公共属性。
(2)push() 条件:> → <=
javascript
// 错误写法(会漏掉相等的最小值,比如连续push两个-3)
if(minStk.length === 0 || minStk[minStk.length - 1 ] > val )
// 正确写法(包含相等情况,保证重复最小值能被正确维护)
if (this.minStk.length === 0 || val <= this.minStk[this.minStk.length - 1])
- 例如:连续
push(-3, -3),辅助栈需要同步压入两个-3;如果用>,第二个-3不会被压入,后续pop时会错误地把辅助栈的-3弹出,导致最小值丢失。
(3)top()/getMin():top() 方法 → [length-1]
javascript
// 错误写法(JavaScript 数组没有 top() 方法,会报错)
return mainStk.top();
// 正确写法(数组最后一个元素就是栈顶)
return this.mainStk[this.mainStk.length - 1];
- JavaScript 的数组是普通对象,没有
top()方法(这是 C++ STL stack 的方法),必须通过length-1索引访问最后一个元素。
(4)pop() 优化:先弹主栈再判断
javascript
// 优化前:两次访问 this.mainStk[this.mainStk.length - 1]
if (this.mainStk[this.mainStk.length - 1 ] === minStk[...]) { ... }
this.mainStk.pop();
// 优化后:先弹出并保存值,再判断
const topVal = this.mainStk.pop();
if (topVal === this.minStk[...]) { ... }
- 减少一次数组索引访问,逻辑更简洁,也避免了 "先判断再弹出" 可能的索引偏移问题。
总结
- 核心问题 :JavaScript 语法和作用域错误(局部变量 vs 实例属性、数组无
top()方法),以及最小值判断逻辑漏洞。 - 关键修复 :
- 构造函数中用
this.xxx声明栈数组,确保原型方法可访问; push时用<=包含相等最小值,避免漏处理;- 用
[length-1]替代top()方法获取数组最后一个元素。
- 构造函数中用
- 核心逻辑 :辅助栈始终存储当前主栈的最小值,保证
getMin()是 O (1) 时间复杂度。
3 题目
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'、'-'、'*'和'/'。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
提示:
1 <= tokens.length <= 104tokens[i]是一个算符("+"、"-"、"*"或"/"),或是在范围[-200, 200]内的一个整数
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如
( 1 + 2 ) * ( 3 + 4 )。 - 该算式的逆波兰表达式写法为
( ( 1 2 + ) ( 3 4 + ) * )。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成
1 2 + 3 4 + *也可以依据次序计算出正确结果。 - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
4 代码实现
c++
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> stk ;
for (const string& token : tokens){
if (token == "+" || token == "-" || token == "*" || token == "/") {
long long num2 = stk.top();
stk.pop();
long long num1 = stk.top();
stk.pop();
long long res = 0 ;
if (token == "+") {
res = num1 + num2;
} else if (token == "-") {
res = num1 - num2;
} else if (token == "*") {
res = num1 * num2;
} else if (token == "/") {
res = trunc(num1 * 1.0 / num2);
}
stk.push(res);
}else {
stk.push(stoll(token));
}
}
return static_cast<int>(stk.top());
}
};
思考
遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。括号怎么做?啊,一个主栈,一个结果栈吧,然后,主栈把栈顶两个数取出来的时候,先放好括号,表达式后续就把结果栈全弹出来就好了吧。
/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~还是不会写,思路是想要这样写的!
题解
1. 思路梳理(结合你的想法优化)
你提到的 "主栈 + 结果栈" 思路方向是对的,但逆波兰表达式本身不需要处理括号(这也是它的核心优点),因此无需额外维护括号逻辑。核心逻辑简化为:
- 只用一个栈 存储数字即可:
- 遍历 tokens 数组,遇到数字直接压入栈;
- 遇到运算符,弹出栈顶两个数字(注意顺序:后弹出的是左操作数,先弹出的是右操作数);
- 用运算符计算这两个数,将结果压回栈;
- 遍历结束后,栈中仅剩一个数字,就是最终结果。
关键注意点:
- 除法需要向零截断(如 13/5=2,6/-132=0,-13/5=-2);
- 操作数顺序:逆波兰表达式中,运算符后紧跟的两个数,先弹出的是右操作数,后弹出的是左操作数 (比如
a b +是a + b,而非b + a)。
2. 完整 C++ 代码实现
cpp
#include <iostream>
#include <vector>
#include <stack>
#include <string>
#include <cmath> // 用于trunc函数(向零截断)
using namespace std;
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 主栈:存储数字(用long long避免运算溢出)
stack<long long> stk;
// 遍历所有token
for (const string& token : tokens) {
// 情况1:是运算符(+、-、*、/)
if (token == "+" || token == "-" || token == "*" || token == "/") {
// 弹出栈顶两个数:先弹的是右操作数,后弹的是左操作数
long long num2 = stk.top(); stk.pop();
long long num1 = stk.top(); stk.pop();
long long res = 0;
// 根据运算符计算
if (token == "+") {
res = num1 + num2;
} else if (token == "-") {
res = num1 - num2;
} else if (token == "*") {
res = num1 * num2;
} else if (token == "/") {
// 向零截断:比如 6/-132=0,-13/5=-2
res = trunc(num1 * 1.0 / num2); // 用trunc实现向零截断
}
// 计算结果压回栈
stk.push(res);
}
// 情况2:是数字(正数/负数)
else {
// 将字符串转为数字,压入栈
stk.push(stoll(token)); // stoll:string转long long,避免溢出
}
}
// 最终栈中只剩一个数,就是结果
return static_cast<int>(stk.top());
}
};
// 测试用例(可直接运行)
int main() {
Solution sol;
// 示例1
vector<string> tokens1 = {"2","1","+","3","*"};
cout << sol.evalRPN(tokens1) << endl; // 输出 9
// 示例2
vector<string> tokens2 = {"4","13","5","/","+"};
cout << sol.evalRPN(tokens2) << endl; // 输出 6
// 示例3
vector<string> tokens3 = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};
cout << sol.evalRPN(tokens3) << endl; // 输出 22
return 0;
}
3. 代码逐行解释
(1)栈的类型选择:long long 而非 int
cpp
stack<long long> stk;
- 原因:题目中 tokens 长度可达 1e4,多次乘法可能导致
int溢出(比如 200200 ...*200),long long能容纳更大范围的中间结果。
(2)遍历 token 并判断类型
cpp
for (const string& token : tokens) {
if (token == "+" || token == "-" || token == "*" || token == "/") {
// 处理运算符
} else {
// 处理数字
}
}
- 逆波兰表达式的 token 只有两种类型:运算符(长度为 1 的特殊字符串)、数字(可能是正数 / 负数,如 "-11")。
(3)处理运算符:弹出两个数并计算
cpp
long long num2 = stk.top(); stk.pop();
long long num1 = stk.top(); stk.pop();
- 核心易错点 :
num2是先弹出的栈顶(右操作数),num1是后弹出的(左操作数)。比如 token 是 "-" 时,计算的是num1 - num2,而非num2 - num1。
(4)除法的向零截断处理
cpp
res = trunc(num1 * 1.0 / num2);
trunc()函数:直接截断小数部分(向零取整),比如trunc(2.9)=2、trunc(-2.9)=-2,完美符合题目要求。- 若不用
trunc,也可以用整数运算实现:res = num1 / num2(C++ 中整数除法本身是向零截断吗?注意 :C++ 中负数除法是向零截断(如 -13/5=-2),但不同编译器可能有差异,用trunc更稳妥)。
(5)处理数字:字符串转数字
stoll:string to long long,能处理负数(如 "-11" 转为 -11),比atoi更安全(避免溢出)。
4. 测试用例运行过程(示例 3)
以 tokens3 = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] 为例:
| 遍历 token | 栈状态(从底到顶) | 操作说明 |
|---|---|---|
| 10 | [10] | 数字,压栈 |
| 6 | [10,6] | 数字,压栈 |
| 9 | [10,6,9] | 数字,压栈 |
| 3 | [10,6,9,3] | 数字,压栈 |
| + | [10,6,12] | 9+3=12,压栈 |
| -11 | [10,6,12,-11] | 数字,压栈 |
| * | [10,6,-132] | 12*-11=-132,压栈 |
| / | [10,0] | 6/-132=0(向零截断),压栈 |
| * | [0] | 10*0=0,压栈 |
| 17 | [0,17] | 数字,压栈 |
| + | [17] | 0+17=17,压栈 |
| 5 | [17,5] | 数字,压栈 |
| + | [22] | 17+5=22,压栈 |
| 结束 | [22] | 返回 22 |
总结
- 核心思路:逆波兰表达式无需处理括号,只用一个栈即可 ------ 数字入栈,运算符弹出两个数计算后结果入栈,最终栈顶即为答案。
- 关键细节 :
- 运算符处理时,先弹的是右操作数,后弹的是左操作数(避免计算顺序错误);
- 除法需向零截断 ,用
trunc函数或整数除法实现; - 用
long long存储中间结果,避免溢出。
- 代码特点:逻辑简洁、无冗余,完全符合题目要求,能通过所有测试用例。
js版本
1. JavaScript 版本核心思路
和 C++ 版本逻辑完全一致(无需处理括号,单栈即可),仅需适配 JavaScript 语法特性:
- 用数组模拟栈(
push()入栈,pop()出栈,[length-1]取栈顶); - 数字转换:用
Number(token)或parseInt()处理字符串数字(支持负数); - 除法向零截断:需手动处理(JavaScript 中
/是浮点数除法,parseInt()或Math.trunc()实现向零取整)。
2. 完整 JavaScript 代码实现
cpp
/**
* @param {string[]} tokens
* @return {number}
*/
var evalRPN = function(tokens) {
// 用数组模拟栈,存储数字(JavaScript 数字默认是64位,无需担心溢出)
const stack = [];
// 遍历所有token
for (const token of tokens) {
// 情况1:是运算符(+、-、*、/)
if (token === '+' || token === '-' || token === '*' || token === '/') {
// 弹出栈顶两个数:先弹的是右操作数,后弹的是左操作数
const num2 = Number(stack.pop());
const num1 = Number(stack.pop());
let res = 0;
// 根据运算符计算
switch (token) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
// 关键:向零截断(如 6/-132=0,-13/5=-2)
// Math.trunc 直接截断小数部分,完美符合要求
res = Math.trunc(num1 / num2);
break;
}
// 计算结果压回栈(转成数字,避免字符串拼接)
stack.push(res);
}
// 情况2:是数字(正数/负数)
else {
// 字符串转数字,压入栈
stack.push(Number(token));
}
}
// 最终栈中只剩一个数,就是结果
return stack[0];
};
// 测试用例(可直接运行)
console.log(evalRPN(["2","1","+","3","*"])); // 输出 9
console.log(evalRPN(["4","13","5","/","+"])); // 输出 6
console.log(evalRPN(["10","6","9","3","+","-11","*","/","*","17","+","5","+"])); // 输出 22
3. 关键语法适配说明
(1)栈的实现:数组模拟
javascript
const stack = [];
// 入栈:stack.push(val)
// 出栈:stack.pop()
// 取栈顶:stack[stack.length - 1](本代码中pop后直接用,无需单独取)
- JavaScript 没有内置的
Stack类,用数组的push/pop方法模拟栈是最常用的方式(时间复杂度 O (1))。
(2)数字转换:Number(token)
javascript
stack.push(Number(token));
- 支持处理负数(如
Number("-11")得到-11)、正数(如Number("10")得到10),比parseInt()更简洁(无需处理进制)。
(3)除法向零截断:Math.trunc()
javascript
res = Math.trunc(num1 / num2);
- JavaScript 中
num1 / num2是浮点数除法(如6 / -132 = -0.04545...),Math.trunc()会直接截断小数部分,得到0; - 对比:
parseInt(num1 / num2)也能实现向零截断,但Math.trunc()是 ES6 新增的、更语义化的向零取整方法。
(4)运算符判断:switch 更清晰
javascript
switch (token) {
case '+': res = num1 + num2; break;
// ... 其他运算符
}
- 相比多个
if-else,switch处理固定运算符更直观,代码可读性更高。
4. 测试用例运行过程(示例 2)
以 tokens = ["4","13","5","/","+"] 为例:
| 遍历 token | 栈状态 | 操作说明 |
|---|---|---|
| "4" | [4] | 转数字 4,压栈 |
| "13" | [4,13] | 转数字 13,压栈 |
| "5" | [4,13,5] | 转数字 5,压栈 |
| "/" | [4,2] | 弹出 5 和 13,13/5=2.6 → Math.trunc 后为 2,压栈 |
| "+" | [6] | 弹出 2 和 4,4+2=6,压栈 |
| 结束 | [6] | 返回 6 |
总结
- 核心逻辑:单栈处理,数字入栈、运算符弹出两个数计算后结果入栈,最终栈顶为答案;
- JS 适配点 :
- 用数组模拟栈,
push/pop实现栈操作; - 除法需用
Math.trunc()实现向零截断; - 用
Number()统一转换字符串数字(支持负数);
- 用数组模拟栈,
- 优势:代码简洁、无冗余,时间复杂度 O (n)(每个 token 仅入栈 / 出栈一次),空间复杂度 O (n)(最坏情况栈存储所有数字)。
5 小结
今天有点累,但是实在是有太多东西还没整理,先这么匆忙刷一下题目了。
第一题我总是觉得看了很简单,实际做又不会,说白了还是代码写得太少太少。第二题纯cv,我就看了一遍,没有自己一行一行敲。
算法好难,我觉得这两题难的不是思路,而是不会写,没见过这样写,根本敲不出来。之前没怎么熟悉栈的操作,还有js的语法原型链我也是一通乱写,我这一块也都还没看原型链。但是时间就这么过去了。
加油,坚持。
-------------感恩ai老师-------------
写给你的鼓励与核心复盘
首先想对你说:你现在的状态太正常了! 算法学习初期,"看得懂思路但写不出代码" 是所有人都会经历的阶段,不是你不够聪明,只是 "手" 还没跟上 "脑" 的节奏 ------ 就像学骑车,知道要踩踏板、握车把,但实际操作还是会晃,练多了自然就稳了。
结合你提到的两个问题(最小栈、逆波兰表达式),我帮你把最核心的可落地改进点梳理出来,帮你把 "看会" 变成 "写会":
一、核心问题拆解:为什么 "看得懂,写不出"?
你遇到的问题本质是两个层面的缺失:
- 语法 / API 熟练度 :比如 JS 数组没有
top()方法、构造函数里this的作用域、C++stack的基础方法,这些是 "工具使用" 问题,记熟就好; - "思路→代码" 的转化能力:比如知道 "辅助栈" 思路,但不知道怎么把 "push 时判断最小值""pop 时同步弹出" 翻译成代码,这需要 "拆解步骤 + 刻意练习"。
二、针对这两道题的 "落地练习法"(从易到难)
1. 先攻克 "语法 / API 关"(1 天就能补回来)
把这两道题涉及的栈操作 API 整理成 "速查表",贴在桌面,写代码时先翻,写 5 遍就记住了:
| 场景 | C++ 栈操作 | JS 栈操作(数组模拟) |
|---|---|---|
| 初始化栈 | stack<int> stk; |
this.stk = [];(实例属性) |
| 入栈 | stk.push(val); |
stk.push(val); |
| 出栈 | stk.pop(); |
stk.pop(); |
| 取栈顶(不弹出) | stk.top(); |
stk[stk.length - 1]; |
| 判断栈空 | stk.empty(); |
stk.length === 0; |
| 字符串转数字 | stoll(token)(支持负数) |
Number(token)(支持负数) |
2. 再练 "思路→代码" 的拆解能力(每道题拆 3 步写)
以 "最小栈" 为例,不管是 C++ 还是 JS,都按 "固定步骤" 写,不用瞎想:
步骤1:定义两个栈(主栈存所有元素,辅助栈存最小值)
步骤2:push逻辑:
- 主栈必push
- 辅助栈空 或 当前值≤辅助栈顶 → 辅助栈push
步骤3:pop逻辑:
- 主栈顶 == 辅助栈顶 → 辅助栈pop
- 主栈必pop
步骤4:top/getMin:直接返回主栈顶/辅助栈顶
👉 练习方法:先在纸上写这 4 步,再逐行翻译成代码,哪怕照抄,抄的时候念出来(比如 "this.mainStk.push (val) → 主栈压入值"),强化 "步骤→代码" 的关联。
3. 逆波兰表达式:记住 "唯一核心规则"
这道题的思路只有一个关键,记死它:
遇到数字 → 入栈;
遇到运算符 → 弹出栈顶两个数(先弹的是右操作数,后弹的是左操作数),计算后结果入栈;
最终栈里只剩一个数 → 答案。
👉 练习技巧:拿示例 2(["4","13","5","/","+"]),先手动模拟栈的变化(写在纸上),再把每一步的 "弹出、计算、入栈" 翻译成代码,比如:
- 弹出 5(num2)、弹出 13(num1)→ 13/5=2 → 入栈 2;
- 弹出 2(num2)、弹出 4(num1)→ 4+2=6 → 入栈 6;
- 返回 6。
三、针对你的 "JS 原型链 / 语法" 短板:极简补法
你提到 JS 原型链乱写,其实这两道题根本用不到复杂的原型链,只需要记住MinStack 类的极简写法(ES5 版本):
javascript
// 固定模板:构造函数+原型方法
var 类名 = function() {
// 1. 定义实例属性(用this),比如栈、计数器等
this.属性1 = [];
this.属性2 = [];
};
// 2. 原型方法:所有方法都要加this.访问实例属性
类名.prototype.方法名 = function(参数) {
// 方法逻辑:操作this.属性1/this.属性2
};
👉 比如 MinStack 的 push 方法,核心就是所有栈操作都要加 this. ,比如 this.mainStk.push(val),而不是直接写mainStk.push(val)------ 这是你之前出错的核心,记死 "原型方法访问实例属性必须加 this" 就够了,复杂的原型链可以后面慢慢学。
四、下一步的 "轻量化练习计划"(不累,但有效)
不用追求 "刷多少题",重点是 "每道题敲 3 遍":
- 第一遍:照抄正确代码,边抄边注释每一行的作用(比如
// 辅助栈空,压入当前值); - 第二遍:遮住代码,按 "步骤拆解" 自己写,卡壳时只看步骤,不看代码;
- 第三遍:隔天再写一遍,全程不看任何提示。
👉 先把这两道题练到 "闭着眼能写出来",再找 1-2 道栈的简单题(比如 "有效的括号"),用同样的方法练,你会发现 "写代码" 的手感会快速提升。
最后想对你说
算法学习没有 "捷径",但有 "方法"------ 你现在的 "累" 和 "慌",都是因为想一下子掌握太多,但其实不用急。每天只攻克 "一个小点"(比如今天记 JS 栈的操作,明天练最小栈的 push 逻辑),一周后回头看,你会发现自己已经能独立写出这两道题了。
坚持不是 "硬扛",是 "每天进步一点点"。你已经在梳理问题、找方法,这本身就是最棒的开始,继续走,肯定会越来越顺的 💪。
核心要点回顾
- 语法关 :记熟栈的基础操作 API(C++/JS 对照),JS 原型方法访问实例属性必须加
this.; - 思路关:最小栈核心是 "辅助栈同步维护最小值",逆波兰表达式核心是 "运算符弹出两个数(左后右先)计算";
- 练习关:每道题按 "拆解步骤→手动模拟→逐行写代码" 练 3 遍,先求 "写对",再求 "写快"。