STL 之 vector 讲练结合

1.vector<T>

1.1 什么是 vector

vector 是 C++ 标准库中提供的一个动态数组容器,它可以自动管理内存,根据需要动态扩容。与普通数组相比,vector 具有以下优势:

  • 动态大小:不需要预先指定大小,可以随时添加或删除元素

  • 自动内存管理:不需要手动 new/delete,避免内存泄漏

  • 丰富的成员函数:提供了大量便捷的操作方法

  • 连续内存存储:与数组一样,可以通过下标随机访问

1.2 vector 的基本使用

头文件与命名空间

cpp 复制代码
#include <vector>   // 必须包含头文件
using namespace std; // 可以省略 std::

创建 vector 对象

cpp 复制代码
// 方式1:创建空的 vector
vector<int> v1;

// 方式2:创建包含 n 个元素的 vector,默认初始化为0
vector<int> v2(10);        // 10个int元素,值为0

// 方式3:创建包含 n 个指定值的 vector
vector<int> v3(5, 100);    // 5个int元素,值都是100

// 方式4:拷贝构造
vector<int> v4(v3);        // v4 是 v3 的副本

// 方式5:C++11 列表初始化
vector<int> v5 = {1, 2, 3, 4, 5};

常用成员函数

函数 功能说明
v.push_back(x) 在尾部添加元素 x
v.pop_back() 删除尾部元素
v.size() 返回元素个数
v.empty() 判断是否为空
v.clear() 清空所有元素
v.resize(n) 改变大小为 n,新增元素默认初始化
v.resize(n, val) 改变大小为 n,新增元素值为 val
v.front() 返回第一个元素的引用
v.back() 返回最后一个元素的引用
v[i] 下标访问(不检查越界)
v.at(i) 下标访问(越界抛异常)

遍历 vector

cpp 复制代码
vector<int> v = {1, 2, 3, 4, 5};

// 方式1:下标遍历
for(int i = 0; i < v.size(); i++) {
    cout << v[i] << " ";
}

// 方式2:迭代器遍历
for(vector<int>::iterator it = v.begin(); it != v.end(); it++) {
    cout << *it << " ";
}

// 方式3:范围for(C++11)
for(auto x : v) {
    cout << x << " ";
}
复制代码

1.3 vector 的内存扩容机制

**重要概念:**vector 有两个重要的大小概念:

  • size:实际存储的元素个数

  • capacity:当前容量(能容纳的最大元素数)

当 size == capacity 时继续添加元素,vector 会自动扩容:通常扩容为原来的 1.5 倍或 2 倍(取决于编译器实现)。

cpp 复制代码
vector<int> v;
cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;

for(int i = 0; i < 10; i++) {
    v.push_back(i);
    cout << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
}

1.4 二维 vector

cpp 复制代码
// 创建 3 行 4 列的二维 vector,初始值为0
vector<vector<int>> v(3, vector<int>(4, 0));

// 访问元素
v[0][0] = 100;

// 创建 vector 数组(N个vector)
const int N = 105;
vector<int> a[N];  // 每个 a[i] 都是独立的 vector
                   // 常用于图的邻接表、链式存储等

1.5 vector 常用算法

cpp 复制代码
#include <algorithm>

vector<int> v = {3, 1, 4, 1, 5, 9};

// 排序
sort(v.begin(), v.end());  // 升序排序

// 翻转
reverse(v.begin(), v.end());

// 查找
auto it = find(v.begin(), v.end(), 5);
if(it != v.end()) {
    cout << "找到了,下标是: " << it - v.begin() << endl;
}

2. 算法题

2.1 询问学号

**题目来源:**洛谷

题目链接: P3156 【深基15.例1】询问学号

难度系数:

【解法】

直接用 vector 或者数组模拟即可。

【参考代码】

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N = 2e6 + 10;
int n, m;
vector<int> a(N);

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    while(m--)
    {
        int x; cin >> x;
        cout << a[x] << endl;
    }
    return 0;
}

2.2 寄包柜

**题目来源:**洛谷

题目链接: P3613 【深基15.例2】寄包柜

难度系数:

【解法】

如果用二维数组来模拟,需要开 10^5 × 10^5 大小的数组,空间会超。但是格子的总数量是 10^7,用数组模拟是完全够用的。因此可以用动态扩容的数组,创建 10^5个 vector 来模拟。

【参考代码】

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N = 1e5 + 10;
int n, q;
vector<int> a[N]; // 创建 N 个柜子

int main()
{
    cin >> n >> q;
    while(q--)
    {
        int op, i, j, k;
        cin >> op >> i >> j;
        if(op == 1) // 存
        {
            cin >> k;
            if(a[i].size() <= j)
            {
                // 扩容
                a[i].resize(j + 1);
            }
            a[i][j] = k;
        }
        else // 查询
        {
            cout << a[i][j] << endl;
        }
    }
    return 0;
}

2.3 移动零

**题目来源:**力扣

题目链接: 283. 移动零

难度系数:

【解法】

在本题中,我们可以用一个 cur 指针来扫描整个数组,另一个 dest 指针用来记录非零数序列的最后一个位置。根据在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。

在 cur 遍历期间,使 0, dest 的元素全部都是非零元素,dest + 1, cur − 1 的元素全是零。

【参考代码】

cpp 复制代码
class Solution
{
public:
    void moveZeroes(vector<int>& nums)
    {
        for(int i = 0, cur = -1; i < nums.size(); i++)
        {
            if(nums[i]) // 非0元素
            {
                swap(nums[++cur], nums[i]);
            }
        }
    }
};

2.4 颜色分类

**题目来源:**力扣

题目链接: 75. 颜色分类

难度系数:

【解法】

类比数组分两块的算法思想,这里是将数组分成三块,那么我们可以再添加一个指针,实现数组分三块。

设数组大小为 n,定义三个指针 left, cur, right:

  • left:用来标记 0 序列的末尾,因此初始化为 −1;

  • cur:用来扫描数组,初始化为 0;

  • right:用来标记 2 序列的起始位置,因此初始化为 n。

在 cur 往后扫描的过程中,保证:

  • 0, left 内的元素都是 0;

  • left + 1, cur − 1 内的元素都是 1;

  • cur, right − 1 内的元素是待定元素;

  • right, n 内的元素都是 2。

【参考代码】

cpp 复制代码
class Solution
{
public:
    void sortColors(vector<int>& nums)
    {
        int n = nums.size();
        int left = -1, right = n, i = 0;
        while(i < right)
        {
            if(nums[i] == 0) swap(nums[++left], nums[i++]);
            else if(nums[i] == 1) i++;
            else swap(nums[--right], nums[i]);
        }
    }
};

2.5 合并两个有序数组

**题目来源:**力扣

题目链接: 合并两个有序数组

难度系数:

【解法】

解法一:利用辅助数组(需要学会,归并排序的核心步骤)

可以创建一个辅助数组,然后用两个指针分别指向两个数组。每次拿出一个较小的元素放在辅助数组中,直到把所有元素全部放在辅助数组中。最后把辅助数组的结果覆盖到 nums1 中。

解法二:原地修改(本题的最优解)

与解法一的核心思想是一样的。

由于第一个数组的空间本来就是 n + m 个,所以我们可以直接把最终结果放在 nums1 中。为了不覆盖未遍历到的元素,定义两个指针指向两个数组的末尾,从后往前扫描。每次拿出较大的元素也是从后往前放在 nums1 的后面,直到把所有元素全部放在 nums1 中。

通过这道题想告诉大家,在我们的算法竞赛中,只要你的空间不超,想用多少辅助数组就用多少辅助数组,怎么方便怎么来。但是在面试中,还是需要注意挖掘最优解。

【参考代码】

法一:利用辅助数组

cpp 复制代码
class Solution
{
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
    {
        // 解法一:利用辅助数组
        vector<int> tmp(m + n);
        int cur = 0, cur1 = 0, cur2 = 0;
        while(cur1 < m && cur2 < n)
        {
            if(nums1[cur1] <= nums2[cur2]) tmp[cur++] = nums1[cur1++];
            else tmp[cur++] = nums2[cur2++];
        }
        while(cur1 < m) tmp[cur++] = nums1[cur1++];
        while(cur2 < n) tmp[cur++] = nums2[cur2++];
        for(int i = 0; i < n + m; i++) nums1[i] = tmp[i];
    }
};

解法⼆:原地修改(本题的最优解)

cpp 复制代码
class Solution
{
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
    {
        // 解法二:原地合并
        int cur1 = m - 1, cur2 = n - 1, cur = m + n - 1;
        while(cur1 >= 0 && cur2 >= 0)
        {
            if(nums1[cur1] >= nums2[cur2]) nums1[cur--] = nums1[cur1--];
            else nums1[cur--] = nums2[cur2--];
        }
        while(cur2 >= 0) nums1[cur--] = nums2[cur2--];
    }
};

补充知识:pair

pair 是 C++ 标准库中的一个模板类,用于将两个值组合成一个单一对象,通常用于存储键值对或返回多个值。它有两个公有成员 first 和 second,分别表示第一个值和第二个值。

我们可以把 pair 理解成 C++ 为我们提供一个结构体,里面有两个变量:

复制代码
cpp 复制代码
struct pair
{
    type first;
    type second;
};

使用的时候,可以指定 first 和 second 为我们想要的任意类型。

指定的方式为 pair<第一个关键字的类型, 第二个关键字的类型>,比如:

复制代码
cpp 复制代码
pair<int, int> p1;      // 第一个 int,第二个 int
pair<long long, int> p2; // 第一个 long long,第二个 int
pair<string, int> p3;    // 第一个 string,第二个 int

不过,一般使用 pair 的时候,上述方式要写很多代码,我们一般会 typedef 一下:

复制代码
cpp 复制代码
typedef pair<int, int> PII;
PII p1;

typedef pair<long long, long long> PLL;
PLL p2;

2.6 The Blocks Problem

**题目来源:**洛谷

题目链接: The Blocks Problem

难度系数:★★

【题目描述】

初始时从左到右有 n 个木块,编号为 0...n − 1,要求实现下列四种操作:

  • move a onto b:把 a 和 b 上方的木块归位,然后把 a 放到 b 上面。

  • move a over b:把 a 上方的木块归位,然后把 a 放在 b 所在木块堆的最上方。

  • pile a onto b:把 b 上方的木块归位,然后把 a 及以上的木块坨到 b 上面。

  • pile a over b:把 a 及以上的木块坨到 b 的上面。

一组数据的结束标志为 quit,如果有非法指令(如 a 与 b 在同一堆),无需处理。

输出:所有操作输入完毕后,从左到右,从下到上输出每个位置的木块编号。

【解法】

本质是一个模拟题,可以用 vector 来模拟,注意细节问题。

【参考代码】

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N = 30;
typedef pair<int, int> PII;

int n;
vector<int> p[N]; // 创建 n 个放木块的槽

PII find(int x)
{
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < p[i].size(); j++)
        {
            if(p[i][j] == x)
            {
                return {i, j};
            }
        }
    }
}

void clean(int x, int y)
{
    // 把 [x, y] 以上的木块归位
    for(int j = y + 1; j < p[x].size(); j++)
    {
        int t = p[x][j];
        p[t].push_back(t);
    }
    p[x].resize(y + 1);
}

void move(int x1, int y1, int x2)
{
    // 把 [x1, y1] 及其以上的木块放在 x2 上面
    for(int j = y1; j < p[x1].size(); j++)
    {
        p[x2].push_back(p[x1][j]);
    }
    p[x1].resize(y1);
}

int main()
{
    cin >> n;
    // 初始化
    for(int i = 0; i < n; i++)
    {
        p[i].push_back(i);
    }

    string op1, op2;
    int a, b;
    while(cin >> op1 >> a >> op2 >> b)
    {
        // 查找 a 和 b 的位置
        PII pa = find(a);
        int x1 = pa.first, y1 = pa.second;
        PII pb = find(b);
        int x2 = pb.first, y2 = pb.second;

        if(x1 == x2) continue; // 处理不合法的操作

        if(op1 == "move") // 把 a 上方归位
        {
            clean(x1, y1);
        }
        if(op2 == "onto") // 把 b 上方归位
        {
            clean(x2, y2);
        }

        move(x1, y1, x2);
    }

    // 打印
    for(int i = 0; i < n; i++)
    {
        cout << i << ":";
        for(int j = 0; j < p[i].size(); j++)
        {
            cout << " " << p[i][j];
        }
        cout << endl;
    }

    return 0;
}

3. 拓展:ACM 模式 vs 核心代码模式

3.1 ACM 模式

ACM 模式一般是竞赛和笔试面试常用的模式,就是只给你一个题目描述,外加输入样例和输出样例,不会给你任何的代码。此时,选手或者应聘者需要根据题目要求,自己完成如下任务:

  1. 头文件的包含

  2. main 函数的设计

  3. 自己定义程序所需的变量和容器(数组、链表、哈希表、字符串等等)

  4. 数据的输入(根据题目叙述控制输入数据的格式)

  5. 数据的处理(各种函数接口的设计)

  6. 数据的输出(根据题目叙述控制返回数据的格式)

总而言之,ACM 模式相当于给你一个空白的代码框,让你自己设计程序来解决问题。

例如:牛客网上一道简单的 ACM 模式的题:牛客学加法

题目描述:

给你两个整数,要求输出这两个整数的和

示例:

输入 1 2

输出 3

此时右边的代码框内一片空白,因此我们需要自己设计程序来解决问题。

ACM 模式代码:

cpp 复制代码
#include <stdio.h> // 自己写头文件

// 自己设计函数接口
int add(int a, int b)
{
    return a + b;
}

int main() // 自己写主函数
{
    int a = 0, b = 0; // 自己定义程序所需的变量或者容器(数组)
    scanf("%d %d", &a, &b); // 自己处理数据的输入
    int c = add(a, b); // 自己设计数据的处理逻辑,以及函数的接口
    //(这里为了方便演示,因此用了函数,其实我们大可不必使用函数)
    printf("%d\n", c); // 自己处理数据的打印
    return 0;
}
相关推荐
牛油果子哥q2 小时前
STL set与map底层精讲,红黑树适配原理、有序去重特性、迭代器遍历、API实战与面试核心考点全解
开发语言·数据结构·c++·面试
奇妙方程式2 小时前
2026年第九届GXCPC广西大学生程序设计大赛(热身赛)题解
c++·编程比赛·编程竞赛·gxcpc
MartinYeung53 小时前
[论文学习]DP2Unlearning:高效且具保证的大型语言模型遗忘框架(基于差分隐私的 LLM Unlearning 方法)
学习·算法·语言模型
Tian_Hang3 小时前
C++原型模式(Protype)
开发语言·c++·算法
bIo7lyA8v3 小时前
算法复杂度的渐进分析与实际运行时间的差异的技术8
算法
yuan199974 小时前
欧拉梁静力与屈曲计算的 MATLAB 实现(有限差分法 + 解析解)
开发语言·算法·matlab
FL16238631294 小时前
[cmake]基于C++使用纯opencv部署ppocrv5v6的onnx模型
开发语言·c++·opencv
玖玥拾4 小时前
C/C++ 数据结构(六)链表迭代器与底层
c语言·数据结构·c++·链表·stl库