
个人主页:
wengqidaifeng
✨ 永远在路上,永远向前走
个人专栏:
数据结构
C语言
嵌入式小白启动!
重要OJ算法题详解
文章目录
- 前言:为什么我们要从"容器"开始?
-
- 一、什么是数据结构?
- 二、数据结构的三要素
-
- [1. 逻辑结构](#1. 逻辑结构)
- [2. 存储结构(物理结构)](#2. 存储结构(物理结构))
- [3. 数据的运算](#3. 数据的运算)
- 三、算法与复杂度
- 四、STL:站在巨人的肩膀上
- 五、顺序表:最熟悉的陌生人
- 六、链表:灵活的动态结构
- 七、栈:后进先出的神奇结构
-
- [7.1 什么是栈?](#7.1 什么是栈?)
- [7.2 手写栈](#7.2 手写栈)
- [7.3 STL中的栈:stack](#7.3 STL中的栈:stack)
- [7.4 算法题实战:有效的括号](#7.4 算法题实战:有效的括号)
- [7.5 栈的其他经典应用](#7.5 栈的其他经典应用)
- 八、队列:先进先出的排队系统
-
- [8.1 什么是队列?](#8.1 什么是队列?)
- [8.2 手写队列](#8.2 手写队列)
- [8.3 STL中的队列:queue](#8.3 STL中的队列:queue)
- [8.4 算法题实战:机器翻译](#8.4 算法题实战:机器翻译)
- [8.5 队列的经典应用](#8.5 队列的经典应用)
- 九、栈和队列的总结对比
- 十、写在最后:如何选择数据结构?
-
- [1. 分析操作需求](#1. 分析操作需求)
- [2. 关注时间复杂度](#2. 关注时间复杂度)
- [3. 优先使用STL](#3. 优先使用STL)
前言:为什么我们要从"容器"开始?
在正式踏上蓝桥杯C++组的备赛之路前,我们首先要面对一个基础却至关重要的问题:如何高效地组织和操作数据?
无论你是在解决一道模拟题、搜索题,还是动态规划题,程序的核心往往都离不开对数据的存储、访问与修改。选择合适的数据结构,不仅能让代码更简洁,更可能直接决定算法能否在时间与空间限制下通过。可以说,数据结构是算法的基石,而STL(标准模板库)则是C++选手手中最锋利的武器。
数据结构:从何而来?
"数据结构"这个词,听起来有些理论化,但它其实源于一个非常朴素的现实需求------如何用计算机更聪明地管理数据。
早期的计算机科学家在处理问题时发现,仅仅依靠基本变量和数组,难以高效应对复杂场景。比如:
- 如何动态地插入、删除大量数据?
- 如何保证数据"先来先服务"或"后来先服务"?
- 如何让数据在内存中既节省空间,又便于访问?
于是,一系列经典的数据结构应运而生:顺序表、链表、栈、队列......它们并非凭空创造,而是针对不同操作场景下"数据组织方式"的抽象与优化。时至今日,它们依然是算法竞赛中最基础、最核心的组成部分。
为什么在C++中更要重视它们?
在C++中,STL为我们提供了这些数据结构的成熟实现:vector(顺序表)、list(链表)、stack(栈)、queue(队列)等。这意味着我们不需要从零手写底层逻辑,而是可以站在巨人的肩膀上,专注于算法本身的实现。
但"会用"不等于"理解"。在蓝桥杯的赛场上,只有真正理解每种结构的特点------比如随机访问与插入删除的权衡、连续内存与链式存储的差异------才能在解题时做出正确的选择,避免踩进"超时"或"内存超限"的坑。
本篇内容预告
作为"数据结构与STL"系列的第一篇,我将从最基础的 线性结构 讲起,依次介绍:
- 顺序表(
vector):连续存储,随机访问快,但中间插入删除慢 - 链表(
list):离散存储,插入删除快,但不支持随机访问 - 栈(
stack):后进先出,解决回溯、表达式求值等经典问题 - 队列(
queue):先进先出,广度优先搜索(BFS)的基石
对于每一种结构,我都会从 底层原理、适用场景、常用操作、注意事项 四个方面展开,并结合蓝桥杯常见的题目类型,帮助你在实战中融会贯通。
------ 工欲善其事,必先利其器。理解数据结构,便是利其器的开始。
一、什么是数据结构?
官方定义:数据结构是一种数据组织、管理和存储的格式。
通俗理解:数据结构就是"数据的组织形式"------研究如何把数据存储在计算机中,以便高效地使用它们。
为什么需要数据结构?
随着计算机处理的数据量越来越大、类型越来越多、关系越来越复杂,我们必须系统地研究数据的特性、数据之间的关系,以及如何有效地组织和管理数据。数据结构这门学科正是为此而生。
二、数据结构的三要素
1. 逻辑结构
逻辑结构描述数据元素之间的逻辑关系,不关心数据在内存中如何存储。
常见的有四种:
| 逻辑结构 | 关系特点 | 例子 |
|---|---|---|
| 集合 | 元素间无特殊关系 | 班级学生名单 |
| 线性结构 | 一对一 | 排队、数组、链表 |
| 树形结构 | 一对多 | 文件夹系统、家族谱 |
| 图结构 | 多对多 | 社交网络、地图导航 |




2. 存储结构(物理结构)
数据在计算机中实际存储的方式:
- 顺序存储:逻辑相邻的元素在物理上也相邻(如数组)
- 链式存储:通过指针建立元素间的联系(如链表)
3. 数据的运算
有了结构和存储方式后,我们需要对数据进行的操作:
- 创建(Create)
- 增(Insert)
- 删(Delete)
- 查(Search)
- 改(Update)
- 排序(Sort)
- 输出(Output)
简单记忆:创 + 增删查改 + 其他
三、算法与复杂度
什么是算法?
算法是解决问题的清晰指令序列,将输入转化为输出。
简单理解:你在C++阶段写的每一个程序,都可以称为一个算法。不要把它想得太高深。
如何衡量算法好坏?
两个核心指标:
| 指标 | 含义 | 关注点 |
|---|---|---|
| 时间复杂度 | 算法执行时间随问题规模的增长趋势 | 运行快慢 |
| 空间复杂度 | 算法占用内存随问题规模的增长趋势 | 内存消耗 |
大O表示法
大O表示法用于粗略估计算法的时间复杂度------只看影响最大的那一项。
推导规则:
- 只保留最高阶项,去掉低阶项
- 去掉最高阶项的常数系数
- 若只有常数项,用1代替
示例:
- T ( N ) = N 2 + 2 N + 10 T(N) = N^2 + 2N + 10 T(N)=N2+2N+10 → O ( N 2 ) O(N^2) O(N2)
- T ( N ) = 1000 T(N) = 1000 T(N)=1000 → O ( 1 ) O(1) O(1)
- T ( N ) = 2 N T(N) = 2N T(N)=2N → O ( N ) O(N) O(N)
常见复杂度对比


O(1) < O(logN) < O(N) < O(NlogN) < O(N²) < O(2^N) < O(N!)
竞赛小贴士 :C++通常1-2秒的时间限制,能承受约 10 7 10^7 107 到 10 8 10^8 108 次运算。
四、STL:站在巨人的肩膀上
什么是STL?
STL(Standard Template Library) 是C++标准库的一部分,包含模板化的通用数据结构和算法。
简单理解:C++已经帮你实现好了很多常用数据结构和算法,直接用就行,避免重复造轮子。
常用的STL组件
| 类别 | 常用组件 | 用途 |
|---|---|---|
| 容器 | vector |
动态数组 |
stack |
栈(后进先出) | |
queue |
队列(先进先出) | |
map |
键值对映射 | |
| 算法 | sort() |
排序 |
find() |
查找 | |
reverse() |
反转 |
怎么学STL?
模仿使用,不求甚解。
STL的实现涉及类、模板、容器适配器等高级知识,竞赛中用不到底层原理。现阶段只需:
- 知道有什么组件可用
- 知道怎么用
- 知道用了会有什么效果
数据结构是算法的基石,而STL是C++选手最锋利的武器。
五、顺序表:最熟悉的陌生人
5.1 什么是顺序表?
如果你学过C语言,一定对数组不陌生。顺序表,就是用数组实现的线性表------数据在逻辑上是连续的,在内存中也是连续存放的。
想象一下电影院的座位:一排椅子紧挨着,每个座位都有固定的编号(下标)。你可以直接找到第3排第5个座位(随机访问),但如果要在中间插入一个人,后面所有人都得往后挪一个位置(插入慢)。
这就是顺序表的核心特征:
- 优点:支持随机访问,按下标取元素的时间复杂度是O(1)
- 缺点:在中间插入或删除元素时,需要移动大量数据,时间复杂度O(n)
5.2 手写一个静态顺序表
虽然比赛中我们通常直接用STL,但理解底层实现能帮你更深刻地掌握它的特性。
cpp
#include <iostream>
using namespace std;
const int N = 1e6 + 10; // 预先分配足够大的空间
int a[N], n; // n表示当前元素个数(从下标1开始存)
// 尾插:在末尾添加元素
void push_back(int x) {
a[++n] = x; // O(1)
}
// 头插:在开头插入元素(需要整体右移)
void push_front(int x) {
for(int i = n; i >= 1; i--) {
a[i + 1] = a[i];
}
a[1] = x;
n++; // O(n)
}
// 按位查找
int at(int p) {
return a[p]; // O(1)
}
// 按值查找
int find(int x) {
for(int i = 1; i <= n; i++) {
if(a[i] == x) return i;
}
return 0; // O(n)
}
// 任意位置插入
void insert(int p, int x) {
for(int i = n; i >= p; i--) {
a[i + 1] = a[i];
}
a[p] = x;
n++; // O(n)
}
// 任意位置删除
void erase(int p) {
for(int i = p + 1; i <= n; i++) {
a[i - 1] = a[i];
}
n--; // O(n)
}
// 清空
void clear() {
n = 0; // O(1)
}
观察上面的代码,你会发现:凡是在中间或头部操作的,都需要移动元素;而只在尾部操作或按下标访问的,都非常快。
5.3 STL中的顺序表:vector
vector 是C++标准库提供的动态顺序表,它会根据需求自动扩容,我们只需要关心如何使用。
常用操作一览
| 操作 | 代码 | 时间复杂度 | 说明 |
|---|---|---|---|
| 创建空vector | vector<int> v; |
O(1) | |
| 创建指定大小 | vector<int> v(n); |
O(n) | 默认初始化为0 |
| 创建并初始化 | vector<int> v = {1,2,3}; |
O(n) | |
| 尾部添加 | v.push_back(x); |
O(1) | 均摊 |
| 尾部删除 | v.pop_back(); |
O(1) | |
| 按下标访问 | v[i] |
O(1) | 不检查越界 |
| 返回首元素 | v.front() |
O(1) | |
| 返回尾元素 | v.back() |
O(1) | |
| 获取元素个数 | v.size() |
O(1) | |
| 判断是否为空 | v.empty() |
O(1) | |
| 改变大小 | v.resize(n); |
O(n) | |
| 清空 | v.clear(); |
O(n) |
代码示例
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 多种创建方式
vector<int> v1; // 空vector
vector<int> v2(5); // 5个元素,默认0
vector<int> v3(5, 10); // 5个元素,都是10
vector<int> v4 = {1, 2, 3, 4, 5}; // 列表初始化
// 尾部操作
v1.push_back(1);
v1.push_back(2);
v1.push_back(3); // v1 = {1, 2, 3}
// 访问元素
cout << v1.front() << " " << v1.back() << endl; // 输出: 1 3
cout << v1[1] << endl; // 输出: 2
// 遍历(三种方式)
// 方式一:下标
for(int i = 0; i < v1.size(); i++) cout << v1[i] << " ";
// 方式二:迭代器
for(auto it = v1.begin(); it != v1.end(); it++) cout << *it << " ";
// 方式三:范围for(最简洁)
for(auto x : v1) cout << x << " ";
// 弹出尾部元素
v1.pop_back(); // v1 = {1, 2}
// 改变大小
v1.resize(5); // v1 = {1, 2, 0, 0, 0}
v1.resize(2); // v1 = {1, 2}
// 清空
v1.clear(); // v1为空
return 0;
}
5.4 算法题实战:询问学号
题目:有n个同学按顺序进入教室,老师想知道第i个进入教室的同学的学号。
分析:这是一道典型的顺序表按位查找问题。由于我们只关心"第几个进入",不需要中间插入删除,用数组或vector存储后直接按下标访问即可。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<int> a(n + 1); // 从下标1开始存,方便理解
for(int i = 1; i <= n; i++) cin >> a[i];
while(m--) {
int x;
cin >> x;
cout << a[x] << endl; // O(1)随机访问
}
return 0;
}
5.5 使用场景小结
什么时候用vector?
- 需要频繁随机访问元素
- 主要在尾部进行插入/删除
- 元素数量不确定但总体可控
什么时候不用vector?
- 需要在头部或中间频繁插入/删除(考虑链表)
- 需要频繁查找某个值(考虑哈希表)
六、链表:灵活的动态结构
6.1 什么是链表?
如果说顺序表是电影院的一排座位,那链表就是一群人手拉手排成一列。每个人只知道自己后面是谁(单链表),或者既知道前面也知道后面(双向链表)。
链表的每个元素叫结点,包含两部分:
- 数据域:存放实际数据
- 指针域:存放指向下一个(或上一个)结点的地址
核心特征:
- 优点:插入和删除非常快,只需修改指针,时间复杂度O(1)
- 缺点 :不支持随机访问,查找某个元素需要从头遍历,时间复杂度O(n)

6.2 手写静态链表
在算法竞赛中,我们通常用数组模拟链表 ,因为用new动态申请结点非常慢,容易超时。
单链表的实现
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// 静态链表:e[i]存数据,ne[i]存下一个结点的位置
int e[N], ne[N], h, id; // h是头指针,id是当前分配的位置
int mp[N]; // 标记数组,记录每个值对应的下标(可选优化)
// 初始化(通常用0作为哨兵位)
void init() {
h = 0; // 哨兵位,不存数据
ne[0] = 0; // 0表示空指针
id = 0;
}
// 头插:在链表头部插入元素
void push_front(int x) {
id++;
e[id] = x;
mp[x] = id;
ne[id] = ne[h]; // 新结点指向原头结点
ne[h] = id; // 哨兵位指向新结点
// 时间复杂度 O(1)
}
// 遍历链表
void print() {
for(int i = ne[h]; i; i = ne[i]) {
cout << e[i] << " ";
}
cout << endl;
}
// 按值查找(遍历)
int find_by_value(int x) {
for(int i = ne[h]; i; i = ne[i]) {
if(e[i] == x) return i;
}
return 0;
}
// 按值查找(标记数组优化,O(1))
int find_fast(int x) {
return mp[x];
}
// 在结点p之后插入新元素
void insert_after(int p, int x) {
id++;
e[id] = x;
mp[x] = id;
ne[id] = ne[p];
ne[p] = id; // O(1)
}
// 删除结点p之后的元素
void erase_after(int p) {
if(ne[p]) {
mp[e[ne[p]]] = 0;
ne[p] = ne[ne[p]]; // O(1)
}
}

双向链表的实现
双向链表比单链表多了一个前驱指针pre[],操作稍微复杂但思路一致:
cpp
const int N = 1e5 + 10;
int e[N], pre[N], ne[N], id, h;
int mp[N];
// 头插
void push_front(int x) {
id++;
e[id] = x;
mp[x] = id;
pre[id] = h;
ne[id] = ne[h];
pre[ne[h]] = id;
ne[h] = id;
}
// 在结点p之后插入
void insert_back(int p, int x) {
id++;
e[id] = x;
mp[x] = id;
pre[id] = p;
ne[id] = ne[p];
pre[ne[p]] = id;
ne[p] = id;
}
// 在结点p之前插入
void insert_front(int p, int x) {
id++;
e[id] = x;
mp[x] = id;
pre[id] = pre[p];
ne[id] = p;
ne[pre[p]] = id;
pre[p] = id;
}
// 删除结点p
void erase(int p) {
mp[e[p]] = 0;
ne[pre[p]] = ne[p];
pre[ne[p]] = pre[p];
}

6.3 STL中的链表:list
STL的list底层是双向循环链表 ,但在竞赛中很少使用,因为动态申请内存(new/delete)效率较低。不过了解它的接口还是很有必要的。
cpp
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lt;
// 尾部插入
lt.push_back(1);
lt.push_back(2); // lt = {1, 2}
// 头部插入
lt.push_front(0); // lt = {0, 1, 2}
// 遍历
for(auto x : lt) cout << x << " ";
// 删除
lt.pop_front(); // lt = {1, 2}
lt.pop_back(); // lt = {1}
return 0;
}
6.4 算法题实战:约瑟夫问题
题目:n个人围成一圈,从第一个人开始报数,数到m的人出列,求依次出列的顺序。
分析 :这是一个经典的循环链表模拟问题。用数组模拟循环链表,每次删除当前结点即可。
cpp
#include <iostream>
using namespace std;
const int N = 110;
int ne[N]; // 记录下一个人的编号
int main() {
int n, m;
cin >> n >> m;
// 构建循环链表
for(int i = 1; i < n; i++) ne[i] = i + 1;
ne[n] = 1; // 首尾相连
int cur = n; // 从n开始,这样第一个移动就会到1
for(int i = 1; i <= n; i++) {
// 移动m-1步,找到要删除的人的前一个
for(int j = 1; j < m; j++) {
cur = ne[cur];
}
// 输出并删除
cout << ne[cur] << " ";
ne[cur] = ne[ne[cur]];
}
return 0;
}
6.5 顺序表 vs 链表:如何选择?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 需要随机访问 | 顺序表 | O(1)访问,链表需要O(n)遍历 |
| 主要在尾部操作 | 顺序表 | push_back是O(1) |
| 频繁在头部/中间插入删除 | 链表 | O(1)修改指针 |
| 需要快速查找某个值 | 顺序表+哈希 | 链表查找是O(n) |
| 内存要求高 | 顺序表 | 链表需要额外指针空间 |
七、栈:后进先出的神奇结构
7.1 什么是栈?
栈是一种只能在某一端进行插入和删除的线性表。就像一摞盘子,你只能从顶部取盘子或放盘子。
- 栈顶:允许操作的一端
- 栈底:不允许操作的一端
- 特性:后进先出(LIFO, Last In First Out)
生活中有很多栈的例子:浏览器的后退功能、编辑器的撤销操作、函数调用栈...
7.2 手写栈
栈的实现非常简单,用数组和一个指针即可:
cpp
const int N = 1e6 + 10;
int stk[N], top; // top指向栈顶元素的下标
// 进栈
void push(int x) {
stk[++top] = x; // O(1)
}
// 出栈
void pop() {
top--; // O(1)
}
// 获取栈顶元素
int top_element() {
return stk[top]; // O(1)
}
// 判空
bool empty() {
return top == 0;
}
// 元素个数
int size() {
return top;
}
7.3 STL中的栈:stack
stack是容器适配器,底层默认用deque实现,使用非常直观:
cpp
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
// 入栈
st.push(1);
st.push(2);
st.push(3);
// 访问栈顶
cout << st.top() << endl; // 输出: 3
// 出栈
st.pop(); // 移除栈顶3
// 遍历(需要边pop边输出)
while(!st.empty()) {
cout << st.top() << " ";
st.pop();
} // 输出: 2 1
return 0;
}
7.4 算法题实战:有效的括号
题目 :给定一个只包含(, ), {, }, [, ]的字符串,判断括号是否有效匹配。
分析:遇到左括号就压栈,遇到右括号就检查栈顶是否匹配。这是栈最经典的场景之一。
cpp
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(char ch : s) {
if(ch == '(' || ch == '[' || ch == '{') {
st.push(ch); // 左括号入栈
} else {
// 右括号但栈为空,无法匹配
if(st.empty()) return false;
char top = st.top();
st.pop();
// 检查是否匹配
if(ch == ')' && top != '(') return false;
if(ch == ']' && top != '[') return false;
if(ch == '}' && top != '{') return false;
}
}
return st.empty(); // 全部匹配完栈应该为空
}
};
7.5 栈的其他经典应用
- 表达式求值:中缀表达式转后缀表达式
- 括号匹配(已演示)
- 函数调用栈:递归的底层实现
- 单调栈:找下一个更大/更小的元素
- 浏览器的后退功能
八、队列:先进先出的排队系统
8.1 什么是队列?
队列是只能在一端插入、另一端删除的线性表。就像排队买票,先来的人先买到票。
- 队头:删除元素的一端(front)
- 队尾:插入元素的一端(back)
- 特性:先进先出(FIFO, First In First Out)
生活中的队列:打印机任务队列、银行叫号系统、消息队列...
8.2 手写队列
队列通常用两个指针实现:h指向队头前一个位置,t指向队尾位置。
cpp
const int N = 1e6 + 10;
int q[N], h, t; // h指向队头前一个位置,t指向队尾
// 入队
void push(int x) {
q[++t] = x; // O(1)
}
// 出队
void pop() {
h++; // O(1)
}
// 获取队头元素
int front() {
return q[h + 1]; // O(1)
}
// 获取队尾元素
int back() {
return q[t]; // O(1)
}
// 判空
bool empty() {
return h == t;
}
// 元素个数
int size() {
return t - h;
}
8.3 STL中的队列:queue
cpp
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
// 入队
q.push(1);
q.push(2);
q.push(3);
// 访问队头/队尾
cout << q.front() << endl; // 输出: 1
cout << q.back() << endl; // 输出: 3
// 出队
q.pop(); // 移除队头1
// 遍历(需要边pop边输出)
while(!q.empty()) {
cout << q.front() << " ";
q.pop();
} // 输出: 2 3
return 0;
}
8.4 算法题实战:机器翻译
题目:内存有M个单元,每次查找单词若内存中没有,则从外存查找并放入内存。内存满时移除最早进入的单词。求查词典次数。
分析 :这是一个经典的队列模拟问题。用队列记录内存中的单词顺序,用布尔数组标记单词是否在内存中。
cpp
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
queue<int> q;
bool in_memory[N]; // 标记单词是否在内存中
int main() {
int m, n;
cin >> m >> n;
int cnt = 0; // 查词典次数
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
if(in_memory[x]) continue; // 内存中有,跳过
cnt++; // 需要查词典
q.push(x);
in_memory[x] = true;
// 内存满了,移除最早进入的单词
if(q.size() > m) {
in_memory[q.front()] = false;
q.pop();
}
}
cout << cnt << endl;
return 0;
}
8.5 队列的经典应用
- 广度优先搜索(BFS):图的层序遍历
- 消息队列:生产者消费者模式
- 滑动窗口:维护窗口内的数据
- CPU调度:时间片轮转
九、栈和队列的总结对比
| 特性 | 栈 (Stack) | 队列 (Queue) |
|---|---|---|
| 操作端 | 一端 | 两端(一端进,一端出) |
| 特性 | 后进先出 (LIFO) | 先进先出 (FIFO) |
| 插入 | push (栈顶) | push (队尾) |
| 删除 | pop (栈顶) | pop (队头) |
| 访问 | top (栈顶) | front/back |
| 典型应用 | 括号匹配、表达式求值、递归 | BFS、消息队列、缓冲 |
十、写在最后:如何选择数据结构?
在算法竞赛中,选择合适的数据结构往往比写出完美算法更重要。这里给你三个建议:
1. 分析操作需求
- 需要随机访问?→ 顺序表(vector)
- 需要频繁插入/删除?→ 链表(但竞赛中少用)
- 需要后进先出?→ 栈
- 需要先进先出?→ 队列
2. 关注时间复杂度
- 读题时预估数据范围:n ≤ 10^5 时 O(n²) 不可行
- 选择容器前思考:每次操作的时间复杂度是否可接受
3. 优先使用STL
除非特别要求手写,否则能用STL就用STL 。vector、stack、queue都是经过高度优化的,比自己手写更快更安全。
下一篇预告:我们将深入探讨树形结构------二叉树、堆与优先队列,这些将是解决更复杂问题的利器。
理解数据结构的本质,不是背诵它们的接口,而是明白"为什么这个结构适合这个问题"。当你真正理解这一点,你就能在赛场上游刃有余。
练习建议:
- 用vector完成洛谷P3156【深基15.例1】询问学号
- 用栈完成洛谷P1739 表达式括号匹配
- 用队列完成洛谷P1540 机器翻译
- 尝试手写静态链表解决洛谷P1160 队列安排
祝你在蓝桥杯的备赛路上越走越远!