1. 前言
1.1 学习背景
大家好!我是刚学完 C 语言、正通过算法题过渡到 C++ 的计算机专业硕士新手。为了系统掌握 C++ 特性与算法基础,我开启了《图解算法与数据结构》85 道经典入门题的刷题计划,这是我的第 04 篇实战笔记。
本篇笔记会完整记录我从「 LeetCode 输入输出理解」到「自己写出普通 OJ 完整代码」的全过程,同时把我踩过的 C++ 语法坑、逻辑坑 全部整理出来,帮和我一样的初学者避坑、吃透核心知识点~
1.2 本篇目标
- 搞懂两种模式:彻底区分 LeetCode 的「类 + 函数」模式 vs 普通 OJ 的「完整程序」模式
- 掌握核心算法:用「双栈法」实现常数时间
getMin()- 避过新手坑:变量作用域、函数调用、拼写错误等一个都不放过
2. 题目描述
2.1 LeetCode 原题(来源:LeetBook《图解算法与数据结构》)
设计一个支持
push,pop,top操作,并能在 常数时间 内检索到最小元素的栈。实现
MinStack类:
MinStack()初始化堆栈对象void push(int val)将元素 val 推入堆栈void pop()删除堆栈顶部的元素int top()获取堆栈顶部的元素int getMin()获取堆栈中的最小元素示例 1:
plaintext
输入: ["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]] 输出: [null,null,null,null,-3,null,0,-2]提示:
-2^31 <= val <= 2^31 - 1pop、top和getMin操作总是在 非空栈 上调用push、pop、top和getMin最多被调用3 * 10^4次
2.2 普通 OJ 输入输出格式(学校上机 / 蓝桥杯 / 牛客常用)
如果这道题出在普通 OJ 上,输入输出会长这样:
输入格式:
第一行一个整数
n,表示操作个数接下来n行,每行一个操作:plaintext
8 push -2 push 0 push -3 getMin pop top getMin输出格式:
对于
top和getMin操作,每行输出一个结果:plaintext
-3 0 -2
3. 我的解题思路
3.1 初始思路(有问题!)
一开始我想:“用 C 语言写过链表栈,C++ 直接用 STL
stack就行。”但写了两行发现不对:
push、pop、top都没问题,都是 O (1)- 但
getMin()要遍历整个栈才能找到最小值,时间复杂度是 O (n),不符合题目 “常数时间” 的要求
3.2 优化思路(双栈法!直达本质)
核心痛点:
getMin()要 O (1),说明我们必须随时记录当前的最小值。解决方案:用两个栈
- 数据栈 A:存所有元素,实现正常的
push/pop/top- 最小栈 B:只存 “当前最小值”,栈顶永远是整个栈的最小值
3.3 实现步骤
- push 操作:
- 数据栈 A 直接压入元素
- 最小栈 B 只有在空 或 新元素 ≤ B 栈顶时才压入(保证 B 栈顶最小)
- pop 操作:
- 如果 A 栈顶 == B 栈顶(说明弹出的是当前最小值),两个栈一起弹
- 否则只弹 A
- top 操作:直接返回 A 栈顶
- getMin 操作:直接返回 B 栈顶(O (1)完美!)
4. 代码实现(C++)+ 逐行注释
4.1 LeetCode 版(只写类,不用管输入输出)
class MinStack {
public:
stack<int> A, B;
MinStack() {}
void push(int x) {
A.push(x);
if(B.empty() || B.top() >= x)
B.push(x);
}
void pop() {
if(A.top() == B.top())
B.pop();
A.pop();
}
int top() {
return A.top();
}
int getMin() {
return B.top();
}
};
4.2 普通 OJ 完整版(带 main 函数,自己处理输入输出)
#include <iostream>
#include <stack>
#include <string>
using namespace std;
// 把栈定义为全局变量:让所有函数都能访问(普通OJ常用写法)
stack<int> A, B;
void push(int x) {
A.push(x);
if (B.empty() || x <= B.top()) {
B.push(x);
}
}
void pop() {
if (A.top() == B.top()) {
B.pop();
}
A.pop();
}
int top() {
return A.top();
}
int getMin() {
return B.top();
}
int main() {
int n;
cin >> n; // 先读操作次数
while (n--) {
string op;
cin >> op;
if (op == "push") {
int x;
cin >> x;
push(x); // 调用我们自己写的 push 函数
}
else if (op == "pop") {
pop(); // 调用我们自己写的 pop 函数
}
else if (op == "top") {
cout << top() << endl; // 注意:是 cout 不是 count!
}
else if (op == "getMin") {
cout << getMin() << endl;
}
}
return 0;
}
4.3 新手 “踩坑版” 代码 + 逐行纠错
#include <iostream>
#include <stack>
#include <string>
using namespace std;
// ❌ 错误1:栈定义在 main 里,上面的函数看不见!
// 应该放到全局,或者封装成类
void push(int x) {
A.push(x);
// ❌ 错误2:条件最好写 x <= B.top(),直接用 x 更清晰
if(B.empty() || B.top()>A.top()){
B.push(x);}
}
void pop() {
if(B.top() == A.top()){
B.pop();
}
A.pop();
}
int top() {
return A.top();
}
int getMin() {
return B.top();
}
int main(){
stack<int> A,B; // ❌ 错误1(续):这里定义的 A、B 只有 main 能用
int n;
cin >> n;
while(n--){
string op;
cin >> op;
if(op == "push"){
int x;
cin >> x;
A.push(); // ❌ 错误3:没传参数 x!而且应该调用我们写的 push(x)
}else if(op == "pop"){
A.pop(); // ❌ 错误4:应该调用我们写的 pop(),否则最小栈 B 不会同步
}else if(op == "top"){
count << A.top(); // ❌ 错误5:拼写错误!是 cout 不是 count
// ❌ 错误6:没加 endl,OJ 判题会格式错误
}else if(op == "getMin"){
count << B.top(); // ❌ 错误5+6:同上
}
}
// ❌ 错误7:main 函数最好加 return 0;
}
5. 核心知识点深度梳理
5.1 双栈法原理(用示例一步一步看)
就拿题目示例来说,我们看两个栈的变化:
操作 数据栈 A 最小栈 B 当前最小值 push(-2) [-2] [-2] -2 push(0) [-2, 0] [-2](0 > -2,不压入) -2 push(-3) [-2, 0, -3] [-2, -3](-3 ≤ -2,压入) -3 getMin() —— —— -3(B 栈顶) pop() [-2, 0] [-2](弹出的 -3 是 B 栈顶,同步弹) -2 top() —— —— 0(A 栈顶) getMin() —— —— -2(B 栈顶) 完美!每一步都在 O (1) 时间内完成。
5.2 LeetCode 模式 vs 普通 OJ 模式(表格对比,一眼分清)
维度 LeetCode 模式 普通 OJ 模式 代码结构 只写类 / 结构体 + 成员函数 写完整程序,必须有 main 输入输出 不用管,系统自动传参收返回值 自己读输入,自己按格式输出 适用场景 LeetCode 专属 学校上机、蓝桥杯、PAT、牛客等
5.3
else ifvselse(含 Python 的elif)因为我之前学过Python,会混淆,现在一句话讲透:
else if(C++/Java)/elif(Python):多个并列、平等的条件,只能命中一个else:前面所有条件都不满足时的 “兜底情况”在这道题里,
push/pop/top/getMin是 4 个并列的操作,没有兜底,所以必须用else if明确判断每一个。
5.4 全局变量 vs 局部变量
- 全局变量:定义在所有函数外面,所有函数都能访问(普通 OJ 写算法题常用,简单省事)
- 局部变量:定义在函数内部(比如 main 里),只有该函数能用
这道题里,
push/pop等函数都要访问栈,所以把栈定义成全局变量最方便。
5.5 C++ 新手拼写易错点
- 输出:是
cout,不是count(count 是 “计数” 的意思)- 换行:输出结果一定要加
endl,否则 OJ 会判格式错误- 函数调用:要调用我们自己封装好的函数(比如
push(x)),不要直接调用 STL 的A.push()
6. 标准解法对比
6.1 思路原理
除了我们学的双栈法,还有一种常见的单栈存
pair法:
- 栈里每个元素存一个
pair<int, int>:第一个值是元素本身,第二个值是 “当前栈的最小值”- 本质和双栈法一样,只是把两个栈的信息合并到了一个栈里
6.2 完整代码实现(单栈 pair 法,作为拓展)
#include <stack>
#include <utility> // 要用 pair
using namespace std;
class MinStack {
private:
// pair<元素值, 当前最小值>
stack<pair<int, int>> st;
public:
MinStack() {}
void push(int val) {
if (st.empty()) {
st.push({val, val});
} else {
st.push({val, min(val, st.top().second)});
}
}
void pop() {
st.pop();
}
int top() {
return st.top().first;
}
int getMin() {
return st.top().second;
}
};
6.3 两种解法复杂度对比
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 双栈法 | O (1)(所有操作都是常数时间) | O (n)(最坏情况最小栈和数据栈一样大) |
| 单栈 pair 法 | O(1) | O (n)(每个元素多存一个最小值) |
两种方法效率一样,双栈法更直观,适合新手理解;pair 法代码更简洁。
7. 学习总结:从 C 到 C++ 的核心收获
这道题是我从 C 过渡到 C++ 的重要一步,核心收获有三个:
- STL 很好用:不用自己手写链表实现栈了,
stack<int>直接用!- 模式要分清:LeetCode 专注算法,只写类;普通 OJ 要完整程序,自己处理输入输出
- 细节定成败:变量作用域、函数调用、拼写、换行…… 这些小地方一不注意就会让代码报错
后续我会继续刷 LeetBook 85 题,记录每道题的学习过程,也欢迎和我一样的新手一起交流、避坑~
如果你觉得这篇笔记对你有帮助,欢迎点赞、收藏、评论区交流! 🚀
&spm=1001.2101.3001.5002&articleId=159865367&d=1&t=3&u=01706f66ac56422d9cfdad298371a71d)
378

被折叠的 条评论
为什么被折叠?



