《STL 六大组件之容器探秘:深入剖析 vector》
目录
- 一、vector 简介
- 二、vector 的常用接口
- 1. 构造函数(constructor function)
- 2. 与迭代器相关的接口(iterator)
- 3. 与容量相关的接口(capacity)
- 4. 与访问和修改有关的接口(access、modify)
- 三、与 vector 相关的精选习题
- 1. 只出现一次的数字
- 2. 杨辉三角
- 3. 删除有序数组中的重复项
- 4. 只出现一次的数字ii
- 5. 只出现一次的数字iii
- 6. 数组中出现次数超过一半的数字
- 7. 电话号码字母组合
一、vector 简介
vecotr 是 C++ STL 中的容器之一,用来存储同类型的多个变量,与数组很相似。但其与数组又有许多不同,vector 是一个类模版,而数组的本质是指针。且使用数组时申请多少空间则只能使用多少空间;而 vector 可以动态增容,空间随着元素的增多而增大。
二、vector 的常用接口
下面从各个方面介绍一下 vector 容器的常用接口,并展示其用法。
1. 构造函数(constructor function)
下面介绍 vector 常用的四个构造函数
(1)函数原型
// 1. 默认构造函数,构造一个空的 vector 对象
vector();
// 2. 构造一个包含 n 个值为 val 的vector 对象
vector(size_type n, const value_type& val = value_type());
// 3. 复制构造函数,构造一个和 v 一样的 vector
vector(const vector& v);
// 4. 该构造函数是一个函数模版,使用一段迭代器来构造该 vector,迭代器可以是任意类型的迭代器
template<class InputIterator>
vector(InputIterator first, InputIterator last);
(2)使用演示
// 1. constructor function
void test1()
{
// 1. 默认构造函数
vector<int> vt_i;
cout << "size_1: " << vt_i.size() << endl;
cout << "capacity_1: " << vt_i.capacity() << endl;
// 2. n 个 val
vector<char> vt_c(5, 'x');
for (const auto& e : vt_c)
cout << e;
cout << endl;
// 3. 复制构造函数
vector<double> vt_d;
// 插入数据
for (int i = 0; i < 10; ++i)
vt_d.push_back(i);
// 复制构造
vector<double> vt_d_cpy(vt_d);
for (const auto& e : vt_d_cpy)
cout << e << " ";
cout << endl;
// 4. 迭代器构造
string str1("Go for it, everyone!");
vector<char> vt_c_1(str1.begin() + 3, str1.end());
for (const auto& e : vt_c_1)
cout << e;
cout << endl;
}
(3)运行结果
2. 与迭代器相关的接口(iterator)
下面简单介绍 vector 常用的 4 个迭代器接口及其用法,主要分为正序迭代器和反序迭代器。
(1)函数原型
// 1. 正序迭代器
iterator begin();
iterator end();
// 2. 反序迭代器
reverse_iterator rbegin();
reverse_iterator rend();
// const 迭代器
// 为 const vector 对象服务
const_iterator begin() const;
const_iterator end() const;
const_reverse_begin() const;
const_reverse_end() const;
(2)使用演示
// 2. iterator
void test2()
{
vector<int> vt_i;
// 插入数据
for (int i = 1; i < 10; ++i)
vt_i.push_back(i);
// 正序遍历
cout << "正序遍历: ";
vector<int>::iterator it = vt_i.begin();
while (it != vt_i.end())
{
// 打印当前迭代器
cout << *it << " ";
// 下一个
++it;
}
cout << endl;
// 逆序遍历
cout << "逆序遍历: ";
vector<int>::reverse_iterator rit = vt_i.rbegin();
while (rit != vt_i.rend())
{
// 打印当前迭代器
cout << *rit << " ";
// 下一个
++rit;
}
cout << endl;
}
(3)运行结果
3. 与容量相关的接口(capacity)
下面介绍 vector 常用的 5 个与容量有关的接口
(1)函数原型
// 1. 返回当前 vector 对象的 size
size_type size() const;
// 2. 返回当前 vector 对象的 capacity
size_type capacity() const;
// 3. 判断当前 vector 对象是否为空
bool empty() const;
// 4. 改变当前 vector 对象的 size
// 如果 n < size,则缩减 size;如果 n > size,则把新增得元素设置为 val;
// 当然,如果 n > capacity,则会进行增容
void resize(size_type n, value_type val = value_type());
// 5. 改变当前对象的 capacity
// 重新开辟一个大小为 n 的空间,把原来的数据拷贝到新的空间(拷贝不了的数据舍弃),释放旧空间;
void reserve(size_type n);
(2)使用演示
// 3. capacity
void test3()
{
vector<int> vt_i;
// 插入数据,同时并观察 vector 对象在 VS 环境下 capacity 的增长情况
cout << "修改前" << endl;
for (int i = 0; i < 20; ++i)
{
vt_i.push_back(i);
cout << "capacity: " << vt_i.capacity() << endl;
}
cout << "size: " << vt_i.size() << endl << endl;
// 改变当前 vector 对象的 capacity
cout << "修改 capacity 后" << endl;
vt_i.reserve(50);
cout << "capacity: " << vt_i.capacity() << endl;
cout << "size: " << vt_i.size() << endl << endl;
// 改变当前对象的 size
cout << "修改 size 后" << endl;
vt_i.resize(25, 0);
cout << "capacity: " << vt_i.capacity() << endl;
cout << "size: " << vt_i.size() << endl << endl;
// 打印当前 vector 对象
for (const auto& e : vt_i)
cout << e << " ";
cout << endl;
}
(3)运行结果
4. 与访问和修改有关的接口(access、modify)
下面介绍 vector 的 6 个与访问和修改有关的接口
(1)函数声明
// 1. 下标运算符重载(分普通 vector 对象和 const vector 对象)
reference operator[](size_type n);
const reference operator[](size_type n) const;
// 2. 尾插、尾删
void push_back(const value_type& val);
void pop_back();
// 3. 插入、删除
iterator insert(iterator position, const value_type& val);
iterator erase(iterator position);
// 4. 交换两个 vector 对象
void swap(vector& v);
(2)使用演示
// 4. access、modify
void test4()
{
vector<int> vt_i;
// 使用尾插插入数据插入数据
for (int i = 1; i <= 10; ++i)
vt_i.push_back(i);
// 使用下标运算符访问 vector 对象中的元素
for (int i = 0; i < 10; ++i)
cout << vt_i[i] << " ";
cout << endl;
// 使用尾删删除数据
for (int i = 0; i < 5; ++i)
vt_i.pop_back();
// 打印尾删后的 vector 对象
for (int i = 0; i < 5; ++i)
cout << vt_i[i] << " ";
cout << endl;
// 插入数据
vt_i.insert(vt_i.begin(), 0);
vt_i.insert(vt_i.end(), 6);
vt_i.insert(vt_i.begin() + 3, 99);
for (const auto& e : vt_i)
cout << e << " ";
cout << endl;
// 删除数据
vt_i.erase(vt_i.begin() + 3);
vt_i.erase(vt_i.end() - 1);
vt_i.erase(vt_i.begin());
for (const auto& e : vt_i)
cout << e << " ";
cout << endl;
}
(3)运行结果
三、与 vector 相关的精选习题
1. 只出现一次的数字
题目链接:link
题目描述:
-
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
-
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例:
提示:
(1)解题思路
两个相同的数异或为 0,而任何数与 0 异或不变。把 vector 中的所有数异或,则最终的结果就是 vector 中只出现一次的数。
(2)解题代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
// 把 vector 对象 nums 中的所有元素异或
// 最终的结果为 nums 中只出现一次的数
int ret = 0;
for (const auto& e : nums)
ret ^= e;
// 返回
return ret;
}
};
2. 杨辉三角
题目链接:link
题目描述:
示例:
提示:
(1)解题思路
把每一行的第一个元素对齐成一列,则所求杨辉三角其实是一个等腰三角形。第一列的元素和正对角线的元素均为 1,其他元素均为其正上方的元素和左上角的元素之和。
注意:这里需要使用 resize 来调整 vector 的 size,这样可以使用下标运算符来访问每个元素。
(2)解题代码
class Solution {
public:
vector<vector<int>> generate(int numRows) {
// 创建二维 vector
vector<vector<int>> vv_i;
// 根据输入的行数确定二维 vector 的大小
vv_i.resize(numRows);
// 给二维 vector 中的每个一维 vector 确定大小
// 并设置每个一维 vector 中元素的值
for (int i = 0; i < numRows; ++i)
{
// 设置每行的大小
vv_i[i].resize(i+1);
// 设置每行的数据
for (int j = 0; j < i + 1; ++j)
{
if (0 == j || i == j)
vv_i[i][j] = 1;
else
vv_i[i][j] = vv_i[i-1][j-1] + vv_i[i-1][j];
}
}
// 返回
return vv_i;
}
};
3. 删除有序数组中的重复项
题目链接:link
题目描述:
示例:
提示:
(1)解题思路
使用前后指针法(pre 和 cur),pre 不动,cur 往后走,当 cur 和 pre 所指对象不同时,++pre 后与 cur 交换。重复上面的步骤,直到 cur 走到末尾。最终 pre 所在位置减去 nums 的初始位置的差值即所求 k 值。
(2)解题代码
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
// 前后指针
int pre = 0, cur = 0;
// cur 往前走,当 cur 和 pre 所指对象不同时交换
while (cur < nums.size())
{
if (nums[cur] != nums[pre])
swap(nums[cur], nums[++pre]);
// 下一个
++cur;
}
// 返回
return pre + 1;
}
};
4. 只出现一次的数字ii
题目链接:link
题目描述:
示例:
提示:
(1)解题思路
由于除了一个数之外,其他的数都出现三次。那么在 32 位的 int 中,为 1 的位出现的次数应该为 3n 或者 3n+1,当出现次数为 3n+1 时,则该位就是只出现一次的数字的为 1 位。当然,有可能为 0,所以最后还原的时候使用数字 0 来还原该数。
(2)解题代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
// 存储 1 出现的次数
int times_one[32] = {0};
for (const auto& e : nums)
{
for (int i = 0; i < 32; ++i)
{
if ((e >> i) & 1)
++times_one[i];
}
}
// 根据为 1 位的出现次数还原该数
int target = 0;
for (int i = 0; i < 32; ++i)
{
if (times_one[i] % 3)
target |= (1 << i);
}
// 返回
return target;
}
};
5. 只出现一次的数字iii
题目链接:link
题目描述:
示例:
提示:
(1)解题思路
把数组中的数全部异或,出现两次的数全部异或为 0,得到的结果就是只出现一次的两个数异或的结果。异或的规则是相同为 0,相异为 1,找出异或结果中为 1 的位,根据该位为 0 还是为 1 分成两组数,这两组数分别异或得到的结果就是只出现一次的两个数。
(2)解题代码
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
// 所有元素异或,得到的结果是两个只出现一次的数的异或
int result = 0;
for (const auto& e : nums)
result ^= e;
// 找出异或结果中为 1 的位
int i = 0;
for (i = 0; i < 32; ++i)
{
if ((result >> i) & 1)
break;
}
// 以该位为分界线,为 1 一组,为 0 一组
// 分别异或,最终的结果就是所求的两位只出现一次的数
int ret1 = 0, ret2 = 0;
for (const auto& e : nums)
{
if ((1 << i) & e)
ret1 ^= e;
else
ret2 ^= e;
}
// 把结果插入 vector 对象
vector<int> ret;
ret.push_back(ret1);
ret.push_back(ret2);
// 返回
return ret;
}
};
6. 数组中出现次数超过一半的数字
题目链接:link
题目描述:
示例:
(1)解题思路
由于目标数字的出现次数超过该数组长度的一般,那么把该数组进行排序,排序过后的数组的中间元素就是目标数字。
(2)解题代码
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int>& numbers) {
// 调用标准库中的 sort 函数进行排序
sort(numbers.begin(), numbers.end());
// 返回该数组的中间元素
return numbers[numbers.size() / 2];
}
};
7. 电话号码字母组合
题目链接:link
题目描述:
示例:
提示:
(1)解题思路
- 本题需要先创建一个字符串数组 digit_to_str,对应数字 2-9 所对应的字符组合;
- 根据输入的数字组合获得对应的字母组合,并尾插到一个空的 vector<string> cur_combi中;
- 重新设计一个递归函数,该函数中包含一个循环,该循环遍历 cur_combi 中的第一个 string 的所有字符,然后递归遍历第二个 string 的所有字符并和第一个 string 中的字符拼接,直到达到 cur_combi 的末尾,最后把拼接好的字符串存放到需要返回的 vector<string> ret 中。
- 本题的解题思路作者描述的不是很好,大家结合着解题代码一起看基本上应该就能看懂,实际上就是 for 循环里放了个递归
(2)解题代码
// 创建一个全局的字符串数组
const char char_combi[10][5] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
class Solution {
public:
void recursion(vector<string>& ret, const vector<string>& cur_combi, int i, int end, string cur)
{
if (i < end)
{
// 遍历当前 string 的每个字符
for (const auto& e : cur_combi[i])
{
// 递归
recursion(ret, cur_combi, i+1, end, cur + e);
}
}
else // 尾插到 ret 中
{
ret.push_back(cur);
}
}
vector<string> letterCombinations(string digits) {
// 如果输入数字组合为空
if ("" == digits)
return vector<string>();
// 获得当前数字组合对应的字母组合
vector<string> cur_combi;
for (const auto& e : digits)
cur_combi.push_back(char_combi[e-'0']);
// 通过递归函数进行递归获取字符串组合
vector<string> ret;
recursion(ret, cur_combi, 0, cur_combi.size(), "");
// 返回
return ret;
}
};