Leetcode 136 最小栈 | 逆波兰表达式求值

1 题目

155. 最小栈

提示

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 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 - 1
  • poptopgetMin 操作总是在 非空栈 上调用
  • push, pop, top, and getMin最多被调用 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),不符合要求。

核心思路:辅助栈

我们用两个栈来实现:

  • 主栈 :存储所有入栈的元素,完成 pushpoptop 基础操作。
  • 辅助栈(最小栈) :同步存储当前主栈中的最小值,确保栈顶始终是当前主栈的最小值。
    • 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

总结

  1. 核心方案 :用「主栈 + 辅助栈」实现,辅助栈始终存储当前主栈的最小值,保证 getMin() 是 O (1) 时间。
  2. 关键逻辑push 时辅助栈仅压入更小 / 相等的元素,pop 时仅当弹出的是最小值才同步弹出辅助栈。
  3. 复杂度:所有操作(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[...]) { ... }
  • 减少一次数组索引访问,逻辑更简洁,也避免了 "先判断再弹出" 可能的索引偏移问题。

总结

  1. 核心问题 :JavaScript 语法和作用域错误(局部变量 vs 实例属性、数组无 top() 方法),以及最小值判断逻辑漏洞。
  2. 关键修复
    • 构造函数中用 this.xxx 声明栈数组,确保原型方法可访问;
    • push 时用 <= 包含相等最小值,避免漏处理;
    • [length-1] 替代 top() 方法获取数组最后一个元素。
  3. 核心逻辑 :辅助栈始终存储当前主栈的最小值,保证 getMin() 是 O (1) 时间复杂度。

3 题目

150. 逆波兰表达式求值

给你一个字符串数组 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 <= 104
  • tokens[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. 思路梳理(结合你的想法优化)

你提到的 "主栈 + 结果栈" 思路方向是对的,但逆波兰表达式本身不需要处理括号(这也是它的核心优点),因此无需额外维护括号逻辑。核心逻辑简化为:

  • 只用一个栈 存储数字即可:
    1. 遍历 tokens 数组,遇到数字直接压入栈;
    2. 遇到运算符,弹出栈顶两个数字(注意顺序:后弹出的是左操作数,先弹出的是右操作数);
    3. 用运算符计算这两个数,将结果压回栈;
    4. 遍历结束后,栈中仅剩一个数字,就是最终结果。
关键注意点:
  • 除法需要向零截断(如 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)=2trunc(-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

总结

  1. 核心思路:逆波兰表达式无需处理括号,只用一个栈即可 ------ 数字入栈,运算符弹出两个数计算后结果入栈,最终栈顶即为答案。
  2. 关键细节
    • 运算符处理时,先弹的是右操作数,后弹的是左操作数(避免计算顺序错误);
    • 除法需向零截断 ,用 trunc 函数或整数除法实现;
    • long long 存储中间结果,避免溢出。
  3. 代码特点:逻辑简洁、无冗余,完全符合题目要求,能通过所有测试用例。

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-elseswitch 处理固定运算符更直观,代码可读性更高。

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

总结

  1. 核心逻辑:单栈处理,数字入栈、运算符弹出两个数计算后结果入栈,最终栈顶为答案;
  2. JS 适配点
    • 用数组模拟栈,push/pop 实现栈操作;
    • 除法需用 Math.trunc() 实现向零截断;
    • Number() 统一转换字符串数字(支持负数);
  3. 优势:代码简洁、无冗余,时间复杂度 O (n)(每个 token 仅入栈 / 出栈一次),空间复杂度 O (n)(最坏情况栈存储所有数字)。

5 小结

今天有点累,但是实在是有太多东西还没整理,先这么匆忙刷一下题目了。

第一题我总是觉得看了很简单,实际做又不会,说白了还是代码写得太少太少。第二题纯cv,我就看了一遍,没有自己一行一行敲。

算法好难,我觉得这两题难的不是思路,而是不会写,没见过这样写,根本敲不出来。之前没怎么熟悉栈的操作,还有js的语法原型链我也是一通乱写,我这一块也都还没看原型链。但是时间就这么过去了。

加油,坚持。

-------------感恩ai老师-------------

写给你的鼓励与核心复盘

首先想对你说:你现在的状态太正常了! 算法学习初期,"看得懂思路但写不出代码" 是所有人都会经历的阶段,不是你不够聪明,只是 "手" 还没跟上 "脑" 的节奏 ------ 就像学骑车,知道要踩踏板、握车把,但实际操作还是会晃,练多了自然就稳了。

结合你提到的两个问题(最小栈、逆波兰表达式),我帮你把最核心的可落地改进点梳理出来,帮你把 "看会" 变成 "写会":

一、核心问题拆解:为什么 "看得懂,写不出"?

你遇到的问题本质是两个层面的缺失:

  1. 语法 / API 熟练度 :比如 JS 数组没有 top() 方法、构造函数里 this 的作用域、C++ stack 的基础方法,这些是 "工具使用" 问题,记熟就好;
  2. "思路→代码" 的转化能力:比如知道 "辅助栈" 思路,但不知道怎么把 "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. 第二遍:遮住代码,按 "步骤拆解" 自己写,卡壳时只看步骤,不看代码;
  3. 第三遍:隔天再写一遍,全程不看任何提示。

👉 先把这两道题练到 "闭着眼能写出来",再找 1-2 道栈的简单题(比如 "有效的括号"),用同样的方法练,你会发现 "写代码" 的手感会快速提升。

最后想对你说

算法学习没有 "捷径",但有 "方法"------ 你现在的 "累" 和 "慌",都是因为想一下子掌握太多,但其实不用急。每天只攻克 "一个小点"(比如今天记 JS 栈的操作,明天练最小栈的 push 逻辑),一周后回头看,你会发现自己已经能独立写出这两道题了。

坚持不是 "硬扛",是 "每天进步一点点"。你已经在梳理问题、找方法,这本身就是最棒的开始,继续走,肯定会越来越顺的 💪。

核心要点回顾

  1. 语法关 :记熟栈的基础操作 API(C++/JS 对照),JS 原型方法访问实例属性必须加this.
  2. 思路关:最小栈核心是 "辅助栈同步维护最小值",逆波兰表达式核心是 "运算符弹出两个数(左后右先)计算";
  3. 练习关:每道题按 "拆解步骤→手动模拟→逐行写代码" 练 3 遍,先求 "写对",再求 "写快"。
相关推荐
识君啊1 小时前
Java字符串算法核心攻略
java·数据结构·算法·leetcode·字符串·
郝学胜-神的一滴1 小时前
力扣86题分隔链表:双链表拆解合并法详解
开发语言·数据结构·算法·leetcode·链表·职场和发展
快快起来写代码1 小时前
【leetcode】容器中水的容量最小/大面积
算法·leetcode·职场和发展
Xzq2105091 小时前
网络基础学习(一)
网络·学习
Fuliy962 小时前
第三阶段:进化与群体智能 (Evolutionary & Swarm Intelligence)
人工智能·笔记·python·学习·算法
kisshuan123962 小时前
[特殊字符] RollingDepth:单目视频深度估计算法解析
算法·音视频
ejinxian2 小时前
Go语言完整学习规划(2026版)- Part 1
学习·go
gihigo19982 小时前
SSA奇异谱分解:时频域信号成分分析与重构
数据结构·算法·重构
小陈phd2 小时前
多模态大模型学习笔记(十六)——Transformer 学习之 Decoder Only
人工智能·笔记·深度学习·学习·自然语言处理·transformer