课程 · C++ 速查 · STL 专题 · 第 2 块

vector:常用的几种写法

学会 vector 的常用动作:开一个、往后加、按下标取、遍历一遍、把要留的挑出来。

第 2 块(共 12 块已写好)14 条动作1 处陷阱2 道例题2 道练习4 份可运行代码

读这一页之前,先知道读完要能做什么

  • 写出来开一个 vector、往末尾加元素、按下标取第 i 个
  • 写出来用 for 遍历一遍,以及用下标遍历(知道什么时候该用后者)
  • 写出来「把符合条件的元素挑进一个新 vector」—— 这一题会反复用到

这是什么

3 段

一句话:vector 就是一个「长度会变的数组」。

★ 尖括号里写「装什么」:vector<int> 装整数,vector<char> 装字符,vector<string> 装字符串。

★ 下标从 0 开始。第 0 个、第 1 个……一直到 size() - 1。

完整动作表

14 条

复杂度写的是这个动作自己的代价,不是整个程序的。「这一栏最容易错的地方」那一列是重点。

写法干什么代价
vector<int> a;开一个空的
⚠ vector<int> 装 int,vector<char> 装 char,两个是不同的类型
O(1)
vector<int> a(n);开 n 个,全是 0
★ 读完 n 之后开数组,这是每道题的第一句
O(n)
vector<int> a(n, 7);开 n 个,全是 7
第二个参数是「每一格的初值」
O(n)
a.push_back(x)在末尾加一个 x
★★ 这是最高频的一个动作
O(1)
a[i]取第 i 个(从 0 数)
⚠ 最后一个是 a[a.size() - 1],不是 a[a.size()]
O(1)
a.size()现在有几个
⚠ 和 int 比大小要写 (int)a.size() < n
O(1)
a.back()取最后一个
空 vector 上调它会出事 —— 先判 a.empty()
O(1)
a.pop_back()扔掉最后一个(不给返回值)
⚠ 要那个值得先 x = a.back(); 存下来
O(1)
a.empty()是不是空的
比 a.size() == 0 短
O(1)
for (int x : a)依次取每个元素,当作 x 用一遍
★★ 只要元素本身、拿不到下标时用它
O(n)
for (int i = 0; i < (int)a.size(); i++)按下标遍历,手里拿着 i
★★ 需要知道「第几个」时用它 —— ⚠ 和上面那个别搞混
O(n)
a.clear()长度清零O(n)
swap(a[i], a[j])把第 i 个和第 j 个对调
标准库自带的,不用自己写交换
O(1)
vector<vector<int>> g(R, vector<int>(C, 0));R 行、每行 C 个 0 的二维表
★★ 动态规划的表全是这个写法;⚠ 内层那个 (C, 0) 不能省
O(R·C)

⭐ 这一节最容易错的地方(1 处)

这一节排在例题前面是故意的:读到一个坑,再去看正确的例子,记得住。 下面每一条都是「编译得过去、答案可能是错的」那种 —— 它们才是 OI 里真正扣分的地方。

只有一处:`a[i]` 下标越界不会报错

a[i] 的 i 超范围时,程序不会报错、不会崩在那一句,它会去读一块不属于它的内存 —— 拿到的可能是别的变量的值,也可能是 0(于是你以为它「有默认值」)。 ★ 所以判断循环次数时那个 (int) 一定要写:for (int i = 0; i < (int)a.size(); i++)。写 i < a.size() 时两边都是无符号数,比较的行为会变得很别扭。

本书哪一章真的用到它

3 处

⚠ 下表里的文件路径和那串 probe 是逐字验过的(check:docs 第 ⑳ 条)—— 改了那份代码这儿会红,正是要的。点章号直接去看那一页。

章书里的哪一份代码用它来干什么
第 1 章code/01-recursion-intro/p1255.cpp★ 全书第一次出现 vector —— 从第 1 章就在用
第 29 章code/29-graph-storage/b3643.cpp★★ 当邻接表用:vector<vector<int>>。那一章专门讲了三种存法的选型
第 53 章code/53-hld/fast.cpp⚠ 一个反例:那一章刻意改用静态数组,因为节点数上界写死在数据范围里

例题(2 道)

每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。先自己读题想一遍,再看代码。

例题 ① 把符合条件的挑进一个新 vector

vec-filter.cpp

给一串整数和一个 k,输出其中所有大于等于 k 的数,按原来的先后顺序。

★ 这一题要练的就是 OI 里最常用的一招:开一个新的 vector,把要留的元素放进去。

输入格式第一行两个整数 n 和 k。第二行 n 个整数。
输出格式原序列,然后是 >= k 的那串数。

输入

9 4
3 7 2 9 4 6 1 8 5

输出

n = 9,k = 4

原序列:
3 7 2 9 4 6 1 8 5 

>= 4 的数(6 个):
7 9 4 6 8 5 
先想一想

别想着「把不要的删掉」。反过来想:要留的是哪些?

vec-filter.cpp参考代码
// STL 专题 · vector —— 把要留的放进一个新 vector
//
// 这一份只练一件事:**新建一个 vector,把符合条件的元素放进去。**
//
// 这是 OI 里最常用的写法,比 v.erase 短、也不会出错:
// vector<int> b;
// for (int x : a) if (条件) b.push_back(x);
//
// ⚠ 输入第一行 n、第二个数 k:输出原序列里所有 >= k 的数,按原来的先后顺序。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int &x : a) cin >> x;
// ★ 全部内容就这三行:开一个新的,把要留的 push_back 进去。
vector<int> b;
for (int x : a) {
if (x >= k) b.push_back(x);
}
cout << "n = " << n << ",k = " << k << "\n\n";
cout << "原序列:\n";
for (int x : a) cout << x << ' ';
cout << "\n\n";
cout << ">= " << k << " 的数(" << b.size() << " 个):\n";
for (int x : b) cout << x << ' ';
cout << '\n';
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
这段代码是怎么想出来的
  1. 第一行:读进来。vector<int> a(n) 开 n 个,然后 for (int &x : a) cin >> x; 读进去。
  2. ★ 注意这里的 &:for (int &x : a) 里的 & 意思是「直接改 a 里的那一格」,所以 cin >> x 真的填进去了。写成 for (int x : a) 就是复制一份出来,读完 a 还是空的。
  3. 核心就这三行:vector<int> b; 开一个新的,for (int x : a) 扫一遍,if (x >= k) b.push_back(x); 把要留的放进去。
  4. 最后 for (int x : b) cout << x << " "; 打出来。
记住这一句

★★ 「开一个新的,把要留的挑进去」是 OI 里用得最多的 vector 写法。它比「边扫边删」短,也不会因为下标错位而漏掉元素。

用到的写法:vector<int> a(n); a.push_back(x) for (int x : a) a.size()

例题 ② 把 vector 当栈用

vec-stack.cpp

给一串只含 () [] {} 的字符,判断括号能不能配平。

★ 这一题练的是「vector 当栈」—— OI 里非常常用,而它不需要 stack。

输入格式一行一个不含空格的串,只由 ( ) [ ] { } 组成。
输出格式一行:配平了打「配平」,否则说明为什么。

输入

([]{}[])

输出

串:([]{}[])

配平 ✓
先想一想

左括号进 push_back,右括号去和最后一个没配上的左括号比 —— 那不就是 back() 吗。

vec-stack.cpp参考代码
// STL 专题 · vector —— 当栈用(OI 里最常见的两种用法之一)
//
// ★ 一句话:`vector<int> st;` + `push_back` / `back` / `pop_back` 就是栈。
// ⚠ 本书里 `stack<int>` 只出现在 4 份代码,而 `vector` 是 1534 份 ——
// 因为 vector 能按下标取、能遍历,stack 都不能。
//
// 这一份练的是「括号匹配」:三种括号,看能不能配平。
//
// ⚠ 输入一行只含 `()` `[]` `{}` 的串。
#include <bits/stdc++.h>
using namespace std;
/** 右括号该配哪个左括号 */
char matchOpen(char c) {
if (c == ')') return '(';
if (c == ']') return '[';
return '{';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin >> s;
// ★ 全部内容就这三行:开一个空的,扫一遍,push_back 进去。
vector<char> st;
bool ok = true;
string why;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push_back(c); // 左括号进栈
} else {
if (st.empty() || st.back() != matchOpen(c)) { // back() 就是栈顶
ok = false;
why = string("第 ") + to_string(&c - &s[0] + 1) + " 个字符 '" + c + "' 配不上";
break;
}
st.pop_back(); // 配上了,弹掉
}
}
cout << "串:" << s << "\n\n";
if (!ok) {
cout << "不配平:" << why << "\n";
} else if (!st.empty()) {
cout << "不配平:还剩 " << st.size() << " 个左括号没人配\n";
} else {
cout << "配平 ✓\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
这段代码是怎么想出来的
  1. 开一个当栈用:vector<char> st;
  2. 扫一遍:for (char c : s)。
  3. 是左括号(( [ {)就 st.push_back(c);。
  4. ★ 是右括号就看 st.back() —— 那就是栈顶:它空着、或者和这个右括号配不上,就不配平;配上了就 st.pop_back();。
  5. 扫完再看 st.empty():不空说明还有左括号没人配。
  6. ⚠ 一个小坑:判断「配不配得上」别写成 st.back() == c —— 右括号和左括号本来就是两个不同的字符,要先想「这个右括号该配哪个左括号」,用一个函数查。
记住这一句

★★ vector<int> st; + push_back / back / pop_back 就是栈。本书里 stack<int> 只出现在 4 份代码,而 vector 有 1534 份 —— 因为 vector 还能按下标取、还能遍历,stack 都不能。

用到的写法:vector<int> a; a.push_back(x) a.back() / a.front() a.pop_back() a.empty()

练习(2 道)

只给题面、样例和一句提示,答案是折叠着的 —— 先自己写一版。

练习 ① 按下标遍历:求最大值和它第一次出现的位置

vec-index.cpp

给 n 个整数,求最大值,以及它第一次出现在第几个(从 1 数)。

★ 关键是分清两种遍历:这里必须用下标,因为要的是「第几个」。

输入格式第一行 n。第二行 n 个整数。
输出格式最大值和它第一次出现的下标。

输入

6
10 30 20 30 50 20

输出

a = 10 30 20 30 50 20 

最大值 50,第一次出现在下标 4(第 5 个)
只用范围 for 求最大值: 50   (对,但不知道位置)
提示

⚠ 先想清楚两件事:for (int x : a) 和 for (int i = 0; ...; i++) 一个拿得到下标、一个拿不到。还有「最大值有多个时取第一个」怎么写。

vec-index.cpp参考答案
记住这一句

★★ 要「第几个」就用下标遍历,只要素值就用范围 for。⚠ 判断「第一个」写 > 不写 >= —— 写 >= 会拿到最后一个。

用到的写法:a[i] for (int i = 0; i < (int)a.size(); i++) for (int x : a)

练习 ② 二维 vector:矩阵转置

vec-2d.cpp

给一个 n × n 的整数表,把它沿主对角线转置((i, j) 和 (j, i) 交换),输出转置后的表。

★ 动态规划的二维表全是 vector<vector<int>> 这么写的 —— 这一题练的就是它。

输入格式第一行 n。之后 n 行,每行 n 个整数。
输出格式原来的表,然后是转置后的表。

输入

4
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

输出

原来的表:
   1   2   3   4
   5   6   7   8
   9  10  11  12
  13  14  15  16

转置之后:
   1   5   9  13
   2   6  10  14
   3   7  11  15
   4   8  12  16
提示

⚠ 三件事:① 二维 vector 那一行怎么写;② 循环怎么只扫上三角(不然就转两次又转回去了);③ i == j 那些格子要跳过。

vec-2d.cpp参考答案
记住这一句

★★ vector<vector<int>> g(R, vector<int>(C, 0)); —— 内层的 (C, 0) 不能省。省了就是 R 个空 vector,g[i][j] 全是垃圾值。

用到的写法:vector<vector<int>> g(R, vector<int>(C, 0)); a[i] swap(a[i], a[j])

做完了去哪儿

← 上一块:前置知识:比较器和「把函数当参数传」 · 回 STL 专题目录 · 下一块:string:读一整行、切词、找子串 →

⚠ 本页说的是 STL 怎么用。算法本身(Dijkstra、单调栈、状压……)在53 章正课里,而每一章末尾都会告诉你它用到了哪些 STL。