栈(Stack)是计算机科学中最基础也最强大的数据结构之一。它遵循后进先出(LIFO)原则,与我们日常生活中的堆叠盘子、浏览器后退按钮等场景高度契合。本文将带你从栈的核心特性出发,结合三个经典算法题(计算器、字符串解码、栈序列验证),深入剖析栈在编程中的应用技巧。无论你使用C++、Python还是JavaScript,都能从中获得启发。
1. 栈的核心特性与基础操作
栈是一种受限的线性表,只允许在表的一端(栈顶)进行插入和删除操作。它的核心特性可以用四个字概括:后进先出。这意味着最后压入栈的元素会最先被弹出。
常见操作:
- push:将元素压入栈顶
- pop:移除栈顶元素
- top/peek:获取栈顶元素但不移除
- empty:判断栈是否为空
在C++中,标准库提供了std::stack容器适配器,底层默认使用deque实现。如果你更倾向于手动控制,也可以像下面代码那样用vector模拟栈行为。
⚠️ 注意事项:栈的空间复杂度通常为O(n),如果递归深度过大,容易导致栈溢出。在Python中,使用列表模拟栈时,append()和pop()的时间复杂度均为O(1),非常高效。


class Solution {
public:
int calculate(string s) {
//用数组来模拟栈
vector st;
int i=0,n=s.size();
char op='+';
while(i='0'&&s[i]<='9'){
int tmp=0;
while(i='0'&&s[i]<='9') tmp=tmp*10+(s[i++]-'0');
if(op=='+') st.push_back(tmp);
else if(op=='-') st.push_back(-tmp);
else if(op=='*') st.back()*=tmp;
else st.back()/=tmp;
}else{
op=s[i];
i++;
}
}
int sum=0;
for(auto e: st){
sum+=e;
}
return sum;
}
};
上述代码展示了如何用数组模拟栈实现一个简易计算器。它通过延迟计算的策略,将加减乘除统一处理,避免了复杂的运算符优先级判断。在实际开发中,如果你使用TypeScript或JavaScript,也可以用数组的push()和pop()完成类似逻辑。
2. 栈的经典应用:字符串解码
字符串解码是栈在解析与编译领域的典型应用。题目要求将形如3[a]2[bc]的字符串解码为aaabcbc。核心思想是:遇到数字时,将其压入数字栈;遇到左括号时,将当前累积的字符串压入字符串栈;遇到右括号时,弹出栈顶的字符串和数字,进行拼接。
关键点:
- 使用两个栈分别存储数字和字符串,避免混淆
- 遇到字母时,持续累积直到遇到数字或括号
- 右括号处理时,注意字符串拼接顺序:先弹出栈顶字符串,再重复k次,最后追加到上一级栈顶字符串
在Go语言中,由于没有内置的栈类型,通常用切片模拟栈操作,与C++的vector思路相似。而在Python中,利用列表的append()和pop()可以写出更简洁的代码。


class Solution {
public:
string decodeString(string s) {
stack nums;
stack st;
st.push("");
int i=0,n=s.size();
while(i='0'&&s[i]<='9'){
int tmp=0;
while(s[i]>='0'&&s[i]<='9'){
tmp=tmp*10+(s[i++]-'0');
}
nums.push(tmp);
}else if(s[i]=='['){
i++;
string tmp="";
while(s[i]>='a'&&s[i]<='z'){
tmp+=s[i++];
}
st.push(tmp);
}else if(s[i]==']'){
string tmp=st.top();
st.pop();
int k=nums.top();
nums.pop();
while(k--){
st.top()+=tmp;
}
i++;
}else{
string tmp;
while(i='a'&&s[i]<='z'){
tmp+=s[i++];
}
st.top()+=tmp;
}
}
return st.top();
}
};
⚠️ 常见错误:忘记处理嵌套括号的情况。例如2[3[a]b],如果只用一个栈,很容易丢失外层信息。正确做法是每次遇到左括号都新建一个栈帧,确保内层解码结果能正确拼接到外层。
3. 栈的验证问题:压入弹出序列
栈的验证问题考察的是对栈操作过程的理解。题目给定两个序列pushed和popped,判断能否通过栈操作实现这样的弹出顺序。核心思路是模拟压栈过程:依次将pushed中的元素压入栈,同时检查栈顶是否与popped当前指针指向的元素相等,如果相等则弹出并移动指针。
✅ 算法步骤:
- 初始化一个空栈,指针
i指向popped的起始位置 - 遍历
pushed数组,将每个元素压入栈 - 每次压入后,检查栈顶是否等于
popped[i],若相等则弹出并i++ - 重复步骤2-3,直到所有元素处理完毕
- 最后判断
i是否等于popped的长度
这个算法的时间复杂度为O(n),空间复杂度为O(n)。在JavaScript中,可以用数组的push()和pop()轻松实现。如果你使用TypeScript,还可以加上类型注解,让代码更健壮。


class Solution {
public:
bool validateStackSequences(vector& pushed, vector& popped) {
int n=popped.size();
stack result;
int i=0;
for(auto e: pushed){
result.push(e);
while(result.size()&&result.top()==popped[i]){
result.pop();
i++;
}
}
return i==n;
}
};
最佳实践:在实际面试或编码中,建议先画图模拟整个过程,再动手写代码。很多开发者容易忽略while循环的条件判断(如栈是否为空),导致空栈访问错误。另外,如果序列长度很大,可以用数组模拟栈来减少函数调用开销,提升性能。
4. 栈在不同语言中的实现对比
不同编程语言对栈的支持各有特色:
- C++:标准库
std::stack,底层默认deque,也可用vector模拟 - Python:直接用
list,append()和pop()时间复杂度O(1) - JavaScript/TypeScript:数组的
push()和pop(),注意shift()是O(n) - Go:用切片模拟,
append()和slice[:len-1]
性能建议:在高频场景下,C++的vector模拟栈通常比std::stack更快,因为减少了包装层的开销。而Python和JavaScript虽然在语法上更简洁,但要注意递归深度限制(通常为1000层),避免栈溢出。
总结
栈作为一种后进先出的数据结构,在表达式求值、括号匹配、深度优先搜索等场景中扮演着核心角色。本文通过计算器、字符串解码、栈序列验证三个经典案例,展示了栈在不同语言(C++、Python、JavaScript、TypeScript、Go)中的灵活运用。掌握栈的本质,不仅能提升算法能力,还能帮助你写出更高效、更简洁的代码。
✅ 核心要点回顾:
- 栈的LIFO特性决定了它的应用场景
- 模拟栈操作时,注意边界条件(空栈、嵌套)
- 不同语言实现栈的性能差异需因地制宜
希望这篇文章能帮你彻底吃透栈的用法,下次遇到类似问题时,能够得心应手!
浙公网安备 33010602011771号