一、括号匹配
输入 {\[\]}()
输出 true
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
stack<char> stk;
bool valid = true;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
stk.push(c);
} else {
if (stk.empty()) {
valid = false;
break;
}
char top = stk.top();
stk.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
valid = false;
break;
}
}
}
if (!stk.empty()) valid = false;
cout << (valid ? "true" : "false") << endl;
return 0;
}
变式1 最长有效括号子串长度❌
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
stack<int> stk;
stk.push(-1); // 哨兵,方便计算长度
int maxLen = 0;
for (int i = 0; i < s.length(); i++) {
if (s[i] == '(') {
stk.push(i);
} else {
stk.pop();
if (stk.empty()) {
stk.push(i); // 重新设置起点
} else {
maxLen = max(maxLen, i - stk.top());
}
}
}
cout << maxLen << endl;
return 0;
}
变式2 括号匹配 + 得分计算❌
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
stack<int> left, star;
for (int i = 0; i < s.length(); i++) {
if (s[i] == '(') {
left.push(i);
} else if (s[i] == '*') {
star.push(i);
} else {
if (!left.empty()) {
left.pop();
} else if (!star.empty()) {
star.pop();
} else {
cout << "false" << endl;
return 0;
}
}
}
// 处理剩余的左括号
while (!left.empty() && !star.empty()) {
if (left.top() > star.top()) {
cout << "false" << endl;
return 0;
}
left.pop();
star.pop();
}
cout << (left.empty() ? "true" : "false") << endl;
return 0;
}
变式3 多括号 + 通配符☑️
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
stack<int> left, star;
for (int i = 0; i < s.length(); i++) {
if (s[i] == '(') {
left.push(i);
} else if (s[i] == '*') {
star.push(i);
} else {
if (!left.empty()) {
left.pop();
} else if (!star.empty()) {
star.pop();
} else {
cout << "false" << endl;
return 0;
}
}
}
// 处理剩余的左括号
while (!left.empty() && !star.empty()) {
if (left.top() > star.top()) {
cout << "false" << endl;
return 0;
}
left.pop();
star.pop();
}
cout << (left.empty() ? "true" : "false") << endl;
return 0;
}
变式4 HTML标签匹配(简单版本)☑️
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
getline(cin, s); // 读取整行,因为 HTML 里有空格
stack<string> stk; // 存标签名,比如 "div"、"p"
int i = 0;
bool valid = true;
while (i < s.length()) {
if (s[i] == '<') { // 遇到 < 说明开始了一个标签
int j = i + 1; // j 指向 < 后面的第一个字符
bool isEnd = (s[j] == '/'); // 判断是不是结束标签 </xxx>
if (isEnd) j++; // 如果是 </,跳过 /
// 找到标签名的结尾(遇到 > 为止)
while (j < s.length() && s[j] != '>') j++;
// 提取标签名
string tag = s.substr(
isEnd ? i + 2 : i + 1, // 起始位置
j - i - 1 - isEnd // 长度
);
if (isEnd) {
// 遇到结束标签 </tag>
if (stk.empty() || stk.top() != tag) {
valid = false; // 栈空或栈顶不匹配 → 非法
break;
}
stk.pop(); // 匹配成功,弹出
} else {
// 遇到开始标签 <tag>
stk.push(tag);
}
i = j + 1; // 跳到 > 后面继续处理
} else {
i++; // 普通字符跳过
}
}
if (!stk.empty()) valid = false; // 栈里还有没匹配的开始标签
cout << (valid ? "true" : "false") << endl;
return 0;
}
二、单调栈
模板 下一个更大元素
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int nums[N], ans[N], stk[N], tt = 0;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> nums[i];
// 从右往左遍历
for (int i = n; i >= 1; i--) {
// 弹出所有比当前元素小的栈顶元素(它们不可能成为左边元素的答案)
while (tt > 0 && stk[tt] <= nums[i]) tt--;
// 栈空说明右边没有更大的,否则栈顶就是答案
ans[i] = (tt > 0) ? stk[tt] : -1;
// 当前元素入栈
stk[++tt] = nums[i];
}
for (int i = 1; i <= n; i++) cout << ans[i] << " ";
return 0;
}
变式1 下一个更大元素 II(循环数组)
题目描述
给定一个循环数组 nums(最后一个元素的下一个是第一个元素),找出每个元素右边第一个比它大的元素。
样例
输入: [1, 2, 1]
输出: [2, -1, 2]
解释:
- 1 右边第一个比它大的是 2
- 2 右边没有更大的,输出 -1
- 1 右边第一个比它大的是 2(因为是循环数组)
核心思路
把数组复制一份接在后面,遍历 2n 个元素,用 i % n 映射回原数组。
代码01
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int nums[N], ans[N], stk[N], tt = 0;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> nums[i];
// 从右向左遍历 2n 个元素
for (int i = 2 * n - 1; i >= 0; i--) {
int idx = i % n;
// 单调递减栈
while (tt > 0 && stk[tt] <= nums[idx]) tt--;
// 只在第一轮(i < n)时记录答案
if (i < n) {
ans[idx] = (tt > 0) ? stk[tt] : -1;
}
stk[++tt] = nums[idx];
}
for (int i = 0; i < n; i++) cout << ans[i] << " ";
return 0;
}
代码02
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int nums[N], ans[N], stk[N], tt = 0;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> nums[i];
ans[i] = -1; // 初始化为 -1
}
// 遍历 2n 个元素,模拟循环数组
for (int i = 0; i < 2 * n; i++) {
int idx = i % n; // 映射回原数组索引
// 栈里存的是索引,不是值
while (tt > 0 && nums[stk[tt]] < nums[idx]) {
ans[stk[tt]] = nums[idx]; // 当前元素是栈顶元素的答案
tt--;
}
stk[++tt] = idx;
}
for (int i = 0; i < n; i++) cout << ans[i] << " ";
return 0;
}
变式2 每日温度
题目描述
给定一个温度列表
temperatures,对于每一天,输出要等多少天才会出现更高的温度。如果没有,输出 0。样例
输入: [73, 74, 75, 71, 69, 72, 76, 73] 输出: [1, 1, 4, 2, 1, 1, 0, 0] 解释: - 第 1 天 73℃,第 2 天 74℃ → 等 1 天 - 第 3 天 75℃,第 7 天 76℃ → 等 4 天 - 第 7 天 76℃,后面没有更高的 → 0核心思路
栈里存索引,遇到比栈顶大的元素就弹出并计算天数差。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int temps[N], ans[N], stk[N], tt = 0;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> temps[i];
for (int i = 1; i <= n; i++) {
// 当前温度比栈顶温度高 → 栈顶的答案就是当前天数
while (tt > 0 && temps[stk[tt]] < temps[i]) {
ans[stk[tt]] = i - stk[tt];
tt--;
}
stk[++tt] = i;
}
// 栈里剩下的元素,答案都是 0(已经初始化为 0)
for (int i = 1; i <= n; i++) cout << ans[i] << " ";
return 0;
}
变式3 柱状图中最大的矩形
题目描述
给定 n 个非负整数,表示柱状图中各个柱子的高度,每个柱子宽度为 1。求该柱状图中能够勾勒出的矩形的最大面积。
样例
输入: [2, 1, 5, 6, 2, 3] 输出: 10 解释: 高度为 5 和 6 的柱子可以组成 5×2=10 的矩形核心思路
对于每根柱子,找到它左边第一个比它矮的和右边第一个比它矮的
这两根柱子之间的宽度 × 当前高度 = 以当前高度为高的最大矩形面积
用单调栈一次遍历找到左右边界
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int heights[N], stk[N], tt = 0;
int leftSmall[N], rightSmall[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> heights[i];
// 找左边第一个比当前矮的柱子
for (int i = 1; i <= n; i++) {
while (tt > 0 && heights[stk[tt]] >= heights[i]) tt--;
leftSmall[i] = tt > 0 ? stk[tt] : 0; // 0 表示左边没有更矮的
stk[++tt] = i;
}
// 清空栈,找右边第一个比当前矮的柱子
tt = 0;
for (int i = n; i >= 1; i--) {
while (tt > 0 && heights[stk[tt]] >= heights[i]) tt--;
rightSmall[i] = tt > 0 ? stk[tt] : n + 1; // n+1 表示右边没有更矮的
stk[++tt] = i;
}
long long maxArea = 0;
for (int i = 1; i <= n; i++) {
long long width = rightSmall[i] - leftSmall[i] - 1;
long long area = width * heights[i];
maxArea = max(maxArea, area);
}
cout << maxArea << endl;
return 0;
}
变式4 接雨水
题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
样例
输入: [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] 输出: 6核心思路
用单调递减栈,遇到比栈顶高的柱子时,说明可以形成凹槽接水。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int height[N], stk[N], tt = 0;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> height[i];
int totalWater = 0;
for (int i = 1; i <= n; i++) {
// 当前高度比栈顶高 → 可以接水
while (tt > 0 && height[stk[tt]] < height[i]) {
int bottom = height[stk[tt]]; // 底部高度
tt--;
if (tt == 0) break; // 左边没有墙了
int left = stk[tt]; // 左边界的索引
int h = min(height[left], height[i]) - bottom; // 水的高度
int w = i - left - 1; // 水的宽度
totalWater += h * w;
}
stk[++tt] = i;
}
cout << totalWater << endl;
return 0;
}
三、出入栈合法性
模板题目
题目描述
给定一个入栈序列
pushSeq和一个出栈序列popSeq,判断popSeq是否可能是pushSeq的一个合法出栈序列。约束
入栈序列是
1, 2, 3, ..., n(按顺序入栈)出栈序列是一个
1~n的排列栈可以随时出栈,但必须遵循 LIFO 规则
样例
输入: n = 5 popSeq = [4, 5, 3, 2, 1] 输出: Yes 解释: 1 入栈 → 栈: [1] 2 入栈 → 栈: [1, 2] 3 入栈 → 栈: [1, 2, 3] 4 入栈 → 栈: [1, 2, 3, 4] 4 出栈 → 栈: [1, 2, 3] → 出栈序列第一个是 4 ✓ 5 入栈 → 栈: [1, 2, 3, 5] 5 出栈 → 栈: [1, 2, 3] → 出栈序列第二个是 5 ✓ 3 出栈 → 栈: [1, 2] → 出栈序列第三个是 3 ✓ 2 出栈 → 栈: [1] → 出栈序列第四个是 2 ✓ 1 出栈 → 栈: [] → 出栈序列第五个是 1 ✓ 输入: n = 5 popSeq = [3, 1, 2, 4, 5] 输出: No 解释: 1 入栈 → 栈: [1] 2 入栈 → 栈: [1, 2] 3 入栈 → 栈: [1, 2, 3] 3 出栈 → 栈: [1, 2] → 出栈序列第一个是 3 ✓ 接下来要出 1,但栈顶是 2,1 被压在下面 → 不可能
完整代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int stk[N], tt = 0;
int main() {
int n;
cin >> n;
vector<int> popSeq(n);
for (int i = 0; i < n; i++) cin >> popSeq[i];
int current = 0; // 当前要匹配的出栈序列索引
bool valid = true;
// 按顺序让 1~n 入栈
for (int i = 1; i <= n; i++) {
stk[++tt] = i; // i 入栈
// 只要栈不空且栈顶等于当前要出栈的元素,就出栈
while (tt > 0 && stk[tt] == popSeq[current]) {
tt--;
current++;
}
}
// 如果所有元素都成功出栈,说明序列合法
if (current == n) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
return 0;
}
变式 1:入栈序列不是 1~n
题目
给定任意入栈序列
pushSeq和出栈序列popSeq,判断是否合法。代码改动 :只需要把
for (int i = 1; i <= n; i++)改成遍历pushSeq即可。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int stk[N], tt = 0;
int main() {
int n;
cin >> n;
vector<int> pushSeq(n), popSeq(n);
for (int i = 0; i < n; i++) cin >> pushSeq[i];
for (int i = 0; i < n; i++) cin >> popSeq[i];
int pushIdx = 0, popIdx = 0;
while (pushIdx < n) {
stk[++tt] = pushSeq[pushIdx++];
while (tt > 0 && stk[tt] == popSeq[popIdx]) {
tt--;
popIdx++;
}
}
cout << (popIdx == n ? "Yes" : "No") << endl;
return 0;
}
变式 2:求所有可能的出栈序列
题目
给定
n,入栈顺序为1, 2, ..., n,求出所有可能的出栈序列。核心思路
DFS + 回溯,模拟每一步是「入栈」还是「出栈」。
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> path; // 出栈序列
stack<int> stk;
void dfs(int pushNum) {
if (pushNum > n && stk.empty()) {
for (int x : path) cout << x << " ";
cout << endl;
return;
}
// 选择 1:出栈(如果栈不空)
if (!stk.empty()) {
int top = stk.top();
stk.pop();
path.push_back(top);
dfs(pushNum);
path.pop_back();
stk.push(top);
}
// 选择 2:入栈(如果还有元素可以入栈)
if (pushNum <= n) {
stk.push(pushNum);
dfs(pushNum + 1);
stk.pop();
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
总结
| 题目 | 核心思想 | 代码要点 |
|---|---|---|
| 判断出栈序列是否合法 | 模拟入栈,匹配就出栈 | 一个循环入栈,内层 while 出栈 |
| 入栈序列不固定 | 同上,但遍历 pushSeq | 两个指针分别跟踪入栈和出栈 |
| 求所有可能出栈序列 | DFS 回溯 | 每一步两种选择:入栈或出栈 |
四、表达式求值
模板题:中缀表达式求值
题目描述
给定一个合法的中缀表达式,包含数字(正整数)、运算符
+ - * /和括号( ),计算结果。样例
输入: (1+2)*(3+4) 输出: 21完整代码
cpp
#include<bits/stdc++.h>
using namespace std;
stack<int> num; // 操作数栈
stack<char> op; // 运算符栈
// 返回运算符优先级
int priority(char c) {
if (c == '+' || c == '-') return 1;
if (c == '*' || c == '/') return 2;
return 0;
}
// 计算一次
void calc() {
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
int res;
if (c == '+') res = a + b;
else if (c == '-') res = a - b;
else if (c == '*') res = a * b;
else res = a / b;
num.push(res);
}
int main() {
string s;
cin >> s;
int i = 0;
while (i < s.length()) {
if (isdigit(s[i])) {
// 提取完整的数字
int x = 0;
while (i < s.length() && isdigit(s[i])) {
x = x * 10 + (s[i] - '0');
i++;
}
num.push(x);
continue; // 跳过 i++,因为已经在循环里处理了
}
if (s[i] == '(') {
op.push('(');
} else if (s[i] == ')') {
// 一直计算直到遇到左括号
while (op.top() != '(') {
calc();
}
op.pop(); // 弹出左括号
} else {
// 运算符:先处理栈里优先级不低于当前运算符的
while (!op.empty() && priority(op.top()) >= priority(s[i])) {
calc();
}
op.push(s[i]);
}
i++;
}
// 处理剩余的运算符
while (!op.empty()) {
calc();
}
cout << num.top() << endl;
return 0;
}
变式 1:只有加减乘除,没有括号
cpp
#include<bits/stdc++.h>
using namespace std;
stack<int> num;
stack<char> op;
int priority(char c) {
if (c == '+' || c == '-') return 1;
return 2;
}
void calc() {
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
int res;
if (c == '+') res = a + b;
else if (c == '-') res = a - b;
else if (c == '*') res = a * b;
else res = a / b;
num.push(res);
}
int main() {
string s;
cin >> s;
int i = 0;
while (i < s.length()) {
if (isdigit(s[i])) {
int x = 0;
while (i < s.length() && isdigit(s[i])) {
x = x * 10 + (s[i] - '0');
i++;
}
num.push(x);
continue;
}
while (!op.empty() && priority(op.top()) >= priority(s[i])) {
calc();
}
op.push(s[i]);
i++;
}
while (!op.empty()) calc();
cout << num.top() << endl;
return 0;
}
变式 2:中缀转后缀(逆波兰表达式)
题目
将中缀表达式转换为后缀表达式(不需要计算结果)。
核心思路
和中缀求值几乎一样,只是遇到数字直接输出,遇到运算符时输出栈里优先级不低于它的运算符。
样例
输入: (1+2)*(3+4) 输出: 12+34+*代码
cpp
#include<bits/stdc++.h>
using namespace std;
int priority(char c) {
if (c == '+' || c == '-') return 1;
if (c == '*' || c == '/') return 2;
return 0;
}
int main() {
string s;
cin >> s;
stack<char> op;
string result;
for (char c : s) {
if (isdigit(c)) {
result += c;
} else if (c == '(') {
op.push(c);
} else if (c == ')') {
while (op.top() != '(') {
result += op.top();
op.pop();
}
op.pop();
} else {
while (!op.empty() && priority(op.top()) >= priority(c)) {
result += op.top();
op.pop();
}
op.push(c);
}
}
while (!op.empty()) {
result += op.top();
op.pop();
}
cout << result << endl;
return 0;
}
变式 3:后缀表达式求值
题目
给定一个后缀表达式(逆波兰表达式),计算结果。
核心思路
遇到数字入栈,遇到运算符弹出两个数字计算,结果入栈。
样例
输入: 1 2 + 3 4 + * 输出: 21代码
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
getline(cin, s);
stack<int> stk;
int i = 0;
while (i < s.length()) {
if (s[i] == ' ') {
i++;
continue;
}
if (isdigit(s[i])) {
int x = 0;
while (i < s.length() && isdigit(s[i])) {
x = x * 10 + (s[i] - '0');
i++;
}
stk.push(x);
} else {
int b = stk.top(); stk.pop();
int a = stk.top(); stk.pop();
int res;
if (s[i] == '+') res = a + b;
else if (s[i] == '-') res = a - b;
else if (s[i] == '*') res = a * b;
else res = a / b;
stk.push(res);
i++;
}
}
cout << stk.top() << endl;
return 0;
}
变式 4:CSP 真题 - 二十四点
题目描述
给定四个数字和三个运算符(只能是
+ - x /),判断能否计算出 24。注意:这里的除法是整数除法,且不考虑括号,按顺序计算。
样例
输入: 1 2 3 4 + - x / 输出: Yes (1+2+3)*4 = 24核心思路
因为只有四个数字和三个运算符,可以直接枚举所有可能的运算顺序(全排列 + 运算符组合),也可以用栈模拟。
cpp
#include<bits/stdc++.h>
using namespace std;
int calc(int a, int b, char op) {
if (op == '+') return a + b;
if (op == '-') return a - b;
if (op == 'x') return a * b;
return a / b;
}
int main() {
int n;
cin >> n;
while (n--) {
string s;
cin >> s;
stack<int> num;
stack<char> op;
for (char c : s) {
if (isdigit(c)) {
num.push(c - '0');
} else {
// 遇到运算符,先处理栈里同级的运算符
while (!op.empty()) {
int b = num.top(); num.pop();
int a = num.top(); num.pop();
num.push(calc(a, b, op.top()));
op.pop();
}
op.push(c);
}
}
while (!op.empty()) {
int b = num.top(); num.pop();
int a = num.top(); num.pop();
num.push(calc(a, b, op.top()));
op.pop();
}
if (num.top() == 24) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
return 0;
}
五、DFS 栈模拟
1. 核心思想
递归的本质就是栈。系统在执行递归函数时,会把每一次函数调用的状态(参数、局部变量、返回地址)压入系统栈。当函数返回时,再从栈中恢复。
栈模拟递归 就是用显式的栈来模拟这个过程,避免系统栈溢出(递归深度太大时会发生)。
模板套路:
定义一个栈,存储当前的状态(节点、参数等)
用一个循环模拟递归的执行流程
遇到需要递归的地方,就把当前状态入栈,然后处理子问题
子问题处理完后,从栈中恢复父问题的状态继续执行
2. 什么时候需要用栈模拟递归
| 场景 | 原因 |
|---|---|
| 递归深度过大(超过 10^5) | 系统栈会溢出 |
| 题目明确要求非递归实现 | 考察对递归本质的理解 |
| 需要自定义遍历顺序 | 栈可以灵活控制 |
| 某些竞赛环境递归栈空间有限 | 防止爆栈 |
二叉树中序遍历的栈实现
cpp
#include<bits/stdc++.h>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> stk;
TreeNode* cur = root;
while (cur != NULL || !stk.empty()) {
// 一直往左走,沿途入栈
while (cur != NULL) {
stk.push(cur);
cur = cur->left;
}
// 弹出栈顶,访问
cur = stk.top();
stk.pop();
result.push_back(cur->val);
// 转向右子树
cur = cur->right;
}
return result;
}
二叉树前序遍历的栈实现
cpp
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
if (root == NULL) return result;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
result.push_back(node->val);
// 先右后左,因为栈是后进先出
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return result;
}
二叉树后序遍历的栈实现
cpp
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
if (root == NULL) return result;
stack<TreeNode*> stk1, stk2;
stk1.push(root);
while (!stk1.empty()) {
TreeNode* node = stk1.top();
stk1.pop();
stk2.push(node);
if (node->left) stk1.push(node->left);
if (node->right) stk1.push(node->right);
}
while (!stk2.empty()) {
result.push_back(stk2.top()->val);
stk2.pop();
}
return result;
}