@TOC
由于详细学习了string类,vector的学习成本很低。下面提出重点进行验证,必要时直接查文档即可(连我自己都不看自己写的这哈哈)。关于vector的灵活运用,还要看题目,比如4.6运用了vector<vector>创建一个m行n列的二维数组等等。关于迭代器失效问题,在模拟实现时已详谈。
正文开始@边通书
vector是动态增长的顺序容器。
" title="">
1. (constuctor) & (destuctor)
:heart: 构造函数
(constructor) | 接口说明 |
---|---|
vector() | 无参构造 |
vector(const vector& x) | 拷贝构造 |
vector (size_type n, const value_type& val = value_type()) | 构造并初始化n个val(value_type即T)少 |
vector (InputIterator first, InputIterator last) | 用迭代器区间初始化 |
演示,调试可观察:
vector<int> v1; //无参构造
vector<int> v2(10); // 构造10个,val
vector<int> v3(++v2.begin(), --v2.end()); //迭代器区间初始化
vector<int> v4(v2); //拷贝构造
思考,string类可以用vector代替吗?不可以,因为有\0
,且要提供一系列字符串专有的接口,如c_str/比较大小/+=
" title="">
:heart: 析构函数
不用管,自动调用析构释放资源。
2. 增删查改
2.0 reserve & resize
Capacity | 说明 |
---|---|
reserve | 扩容 |
resize | 扩容+初始化 / 删除数据(capacity不变,防止抖动) |
- resize初始化可以自己指定值,也可以使用类型的缺省值。如int类型的缺省值为0、指针类型缺省值为nullptr。
:heart: vector是如何增容的?
#include<iostream>
#include<vector>
using namespace std;
int main()
{
size_t sz;
vector<int> foo;
sz = foo.capacity();
cout << "making foo grow:\n";
for (int i = 0; i<100; ++i)
{
foo.push_back(i);
if (sz != foo.capacity())
{
sz = foo.capacity();
std::cout << "capacity changed: " << sz << '\n';
}
}
}
在vs下,大约是1.5倍增长 ——
" title="">
g++以标准的2倍增长 ——
" title="">
2.1 push_back & pop_back
尾插尾删。
2.2 find
vector类中并没有find,这是因为算法库中就提供了一个模板函数,在迭代器区间中查找(左闭右开),若查找到了,就返回迭代器;没找到就返回s.end()
" title="">
字符串中有find,是因为不仅要查字符,还要查子串。
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 3);
if (it != v.end())
{
cout << "找到了!" << *it << endl;
}
2.3 insert & erase
可头插头删。
Modifiers | 说明 |
---|---|
insert | 在position(迭代器)之前插入val |
erase | 删除position(迭代器)位置的数据 |
insert & erase 可以搭配 find 使用。string类的insert和erase通常支持下标,因为它find也刚好返回下标。
// 头插
v.insert(v.begin(), 0);
3. 遍历
先尾插一下1,2,3,4.
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
3.1 []
// 遍历 - 可读可写
for (size_t i = 0; i < v.size(); i++)
{
v[i]++;
cout << v[i] << " ";
}
cout << endl;
运行结果 ——
" title="">
3.2 迭代器 & 范围for
:heart: iterator 内嵌类型
// 遍历 - 可读可写
vector<int>::iterator it = v.begin();
while (it != v.end())
{
*it -= 1;
cout << *it << " ";
it++;
}
cout << endl;
运行结果 ——
" title="">
支持迭代器就支持范围for:candy:
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
4. 解题报告
4.1 只出现一次的数字i
题目链接:136. 只出现一次的数字 - 力扣(LeetCode) (leetcode-cn.com)
著名的单身狗问题,可查看我《剑指offer》专栏。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int a = 0;
for(auto e : nums)
{
a ^= e;
}
return a;
}
};
4.2 只出现一次的数字ii
题目链接:137. 只出现一次的数字 II - 力扣(LeetCode) (leetcode-cn.com)
思路:考虑数字的二进制形式,对于出现三次的数字,各 二进制位 出现的次数都是 3 的倍数。因此,统计所有数字的 各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字。妙啊太妙了!" title="">
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
size_t sum = 0; // 统计个二进制位中1的个数
for(size_t i = 0; i<32; i++)
{
sum = 0;
for(auto e : nums)
{
sum += (e>>i) & 1;
}
sum %= 3;
ret += (sum<<i);
}
return ret;
}
};
4.3 只出现一次的数字iii
题目链接:260. 只出现一次的数字 III - 力扣(LeetCode) (leetcode-cn.com)
著名的两只单身狗的故事。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
// 1.把所有数字异或在一起
int ret = 0;
for(auto e : nums)
{
ret ^= e;
}
// 2.找到这两个单身狗数字不同的一位(二进制)
size_t pos = 0;
while(pos < 32)
{
if((ret >> pos) & 1 == 1)
break;
pos++;
}
// 3.分组,边分组边异或
vector<int> retArr(2);
for(size_t i = 0;i < nums.size();i++)
{
if((nums[i] >> pos)&1 == 1)
{
retArr[0] ^= nums[i];
}
else
{
retArr[1] ^= nums[i];
}
}
return retArr;
}
};
4.4 删除有序数组中的重复项
题目链接:26. 删除有序数组中的重复项 - 力扣(LeetCode) (leetcode-cn.com)
这题顺序表那儿就做过,可查看我数据结构经典题目专栏。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int dst = 0;
int i = 0;
int j = 1;
while(i < nums.size())
{
while(j < nums.size() && nums[i] == nums[j])
{
j++;
}
nums[dst] = nums[i];
dst++;
i = j;
}
return dst;
}
};
4.5 杨辉三角OJ
题目链接:118. 杨辉三角 - 力扣(LeetCode) (leetcode-cn.com)
在C语言中,动态开辟创建一个m行n列的二维数组相当麻烦,释放也很麻烦。有了vector<vector>就简洁很多。
" title="">
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
vv.resize(numRows);
// 1.开空间 + 初始化
for(size_t i = 0; i < numRows ;i++ )
{
vv[i].resize(i+1);
vv[i][0] = vv[i][i] = 1;
}
// 2.二维数组遍历
for(size_t i = 0;i < vv.size(); i++)
{
for(size_t j = 0; j < vv[i].size();j++)
{
if(vv[i][j] == 0)
vv[i][j] = vv[i-1][j-1]+ vv[i-1][j];
}
}
return vv;
}
};
4.6 电话号码组合
题目链接:17. 电话号码的字母组合 - 力扣(LeetCode) (leetcode-cn.com)
这是一道多路递归的深度遍历,简单的映射和回溯。递归深度取决于数字串长度;数字映射到的字符串多长,就是几路递归。
" title="">
class Solution {
public:
// 单参数构造
string arr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
void _letterCombinations(string& digits,size_t i,string comStr,vector<string>& v)
{
if(i == digits.size())
{
v.push_back(comStr);
return ;
}
string str = arr[digits[i]-'0'];
for(size_t j = 0; j < str.size(); j++)
{
_letterCombinations(digits,i+1,comStr + str[j],v);
}
}
vector<string> letterCombinations(string digits) {
vector<string> ret;
string comStr;
if(digits.empty())
return ret;
_letterCombinations(digits, 0, comStr,ret);
return ret;
}
};
注意:
- 建立映射:不需要vector,因为是定长的,不用动态增长的。并且可以像
string("abc")
这样初始化,因为是单参数的构造函数支持隐式类型转换。 - 借助comstr拼这个串儿。注意,上述代码不传引用,若传引用,则回过来时需要pop_back,如下。
class Solution {
public:
// 单参数构造
string arr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
void _letterCombinations(string& digits,size_t i,string comStr,vector<string>& v)
{
if(i == digits.size())
{
v.push_back(comStr);
return ;
}
string str = arr[digits[i]-'0'];
for(size_t j = 0; j < str.size(); j++)
{
_letterCombinations(digits,i+1,comStr + str[j],v);
}
}
vector<string> letterCombinations(string digits) {
vector<string> ret;
string comStr;
if(digits.empty())
return ret;
_letterCombinations(digits, 0, comStr,ret);
return ret;
}
};
不清晰的可以去画递归展开图,在脑子里递也是蛮有感觉的。
" title="">
持续更新~@边通书