目录
一.序言
本质来讲,vector类就是我们之前数据结构所实现的顺序表.
和数组不同,它的大小是可以自动进行调节,而不是固定不变,并且由容器来管理内存,和数组,
String一样,都可以通过[ ]高效进行访问数据.
我们同样可以去cplusplus.com网站查看有关vector类更为详细的官方介绍
有了string类的基础,可以说上手vector类的成本是很低的,并且由于vector类是属于STL库的,相
比于string类来说,重复繁杂的接口反而减少了很多.
注意:在使用vector对象的时候,同样需要包含#include头文件以及using namespace std(直接展开或间接展开);
后面的程序可能有部分省略了这部分代码.
二.vector类常用接口说明
1.vector类的常见构造
1.构造空的vector对象
由于是模板实现,所以vector类创建的对象,可以是任意类型(其实就是顺序表中所存元素可以是任
意类型),只需要创建对象的时候,显示指定即可.
vector <int> first;
2.用n个相同元素初始化vector
//用10个1,给vector类初始化
vector <int> second(10, 1);
3.用迭代器构造
注意是左闭右开区间,按照对象迭代器的顺序进行构造.
vector <int> third(second.begin(), second.end());
4.拷贝构造
利用另一个对象进行拷贝构造对象,顺序保持不变.
比如可以利用上面的third对象创建fourth对象.
vector <int> fourth(third);
2.vecotr类的迭代器
和String类几乎完全相同,vector类也提供begin,end等等迭代器,同时也支持函数重载,所以
cbegin,cend等等函数其实不常用.
和String类似,vector类的迭代器,我们依旧可以把它看作是一个指针,然后对它进行解引用,移
动等等操作.
比如说下面的代码,就可以输出vector对象相应的值.
vector <int> v(10,1);
vector <int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it;
it++;
}
cout << endl;
for (auto e : v)
{
cout << e;
}
cout << endl;
当然如果要反向进行遍历,调用rbegin,rend搭配使用也是可以的.
vector <int> v;
for (size_t i = 0; i < 10; ++i)
v.push_back(i);
cout << "my vector contains:";
auto rit = v.rbegin();
while (rit != v.rend())
{
cout << " " << * rit ;
rit++;
}
cout << endl;
3.vector类的空间管理
和String类类似,vector类也提供了类似的接口.
在用法和功能上上,和String类几乎完全一样.
这里只简单挑reserve接口进行简单讲解.
reserve接口也和String的类似,当调整的空间小于当前空前容量,其实没有任何变化,如果需要空
间大于当前空间容量,才进行扩容,我们同样可以用之前类似的小程序进行验证.
size_t sz = 0;
vector <int> v;
for (size_t i = 0; i < sz; ++i)
v.push_back(i);
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << endl;
}
}
vector<int> bar;
sz = bar.capacity();
bar.reserve(100); // this is the only difference with v above
cout << "making bar grow:\n";
for (int i = 0; i < 100; ++i) {
bar.push_back(i);
if (sz != bar.capacity())
{
sz = bar.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
如果可以提前知道大概需要多少空间,提前将容量设置好,避免边插入边扩容效率低
PS:在vs下空间大致按照1.5倍进行扩容,而Linux下大致按照2倍扩容.
4.vector类的增删查改
具体用法和String类的接口类似,这里不再过多讲解.
值得注意的是,我们注意到vector类本身并没有提供find接口,那对于insert,erase等等接口来
说,那找pos位置,进行插入,删除应该如何操作呢?
答案是库里面算法有专门find接口提供.
虽然我们可以看到,它的实现其实还是逐一进行遍历,但至少给我们编写程序提供便捷.
//C++11提供的使用列表进行初始化
vector <int>v{ 1,2,3,4 };
auto pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
//在pos位置前插入100
v.insert(pos, 100);
}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//重新返回迭代器,否则会报错
pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据
v.erase(pos);
it = v.begin();
while (it != v.end()) {
cout << *it << " ";
++it;
}
cout << endl;
三.有关vector类的题目
1.只出现一次的数字
严格来说,其实这和vector类的运用关系不多,主要涉及vector的遍历,关键思想反而是异或的运
用.
class Solution {
public:
int singleNumber(vector<int>& nums) {
int num = 0;
for (auto e:nums)
{
num ^= e;
}
return num;
}
};
2.杨辉三角
class Solution {
public:
vector<vector<int>> generate(int numRows) {
//构建一个vector类,并且每个元素类型为vector<int>
vector <vector<int>> vv;
vv.resize(numRows,vector<int>());
//初始化,并且给每个vector的开头或者结尾赋上相应的值1
for (size_t i = 0;i < numRows;++i)
{
vv[i].resize(i + 1,0);
vv[i][0] = vv[i][vv[i].size() - 1] = 1;
}
//遍历二维数组每一个元素,并求解相应位置的值
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] + vv[i - 1][j - 1];
}
}
return vv;
}
};
有了C++的vector类,二维数组在我们这里,得到大大的简化,最起码的一点是,我们再也不用像
之前一样,和二级指针打交道.
虽然这道题用C也同样可以实现,但在可读性上,明显C++的优势开始发挥出来.
int** generate(int numRows, int* returnSize, int** returnColumnSizes){
//每行有多少个元素,需要一个一维数组返回,所以传进来二级指针
*returnColumnSizes = (int*)malloc(sizeof(int)*numRows);
//记录数组行数
*returnSize = numRows;
//创建新矩阵,每个元素都是一个一级指针,指向一个一维数组
int** anw = (int**)malloc(sizeof(int*)*numRows);
for (int i = 0;i < numRows;++i)
{
//对应每一行的列数,记录下来
(*returnColumnSizes)[i] = i + 1;
//每一行分配一个一维数组,对应长度为行数+1
anw[i] = (int*)malloc(sizeof(int)*(i + 1));
//一维数组对应首尾赋值为1
anw[i][0] = anw[i][i] = 1;
//其余位置,由上一行左方与上一行右方相加得到
for (int j = 1;j < i;++j)
{
anw[i][j] = anw[i - 1][j - 1] + anw[i - 1][j];
}
}
return anw;
}
3.删除有序数组中的重复项
26. 删除有序数组中的重复项 - 力扣(LeetCode)
严格来说,这道题,也不是主要考察vector类的知识,其本质考查的是快慢指针的知识,设立slow
慢指针,始终指向新数组的最后一个位置,fast指针在前面找不同元素,和快排的快慢指针实现倒
是非常相近.
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() < 1) return 0;
int slow = 0;
for (int i = 1; i < nums.size(); i++)
{
if (nums[slow] != nums[i])
{
nums[++slow] = nums[i];
}
}
return (slow + 1);
}
};
4.数组中出现超过一半的数字
数组中出现次数超过一半的数字_牛客题霸_牛客网 (nowcoder.com)
众数也就是出现次数最多的数字,换句话说,就是数量最多的数字,假如比拼人数的话,其它所有
数字都比不过它.
因此我们可以假定第一个数字是众数,并且给一个计数器cnt,初始为1,如果遇到相同的数字,
那计数器增加,否则减少,一旦减为0,则这个数字至少在现在不可能是我们的众数,我们进行相
应调整,那存活到最后的数,就是我们要求的众数.
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
size_t mode = numbers[0];
size_t cnt = 1;
for (size_t i = 1;i < numbers.size();++i)
{
//如果和当前假定众数相同,则计数+1
if (numbers[i] == mode) cnt++;
//不同,则计数-1
else cnt--;
//由于保证有结果,所以众数计数绝对最后不为0
if (cnt == 0)
{
mode = numbers[i];
cnt = 1;
}
}
return mode;
}
};
5.只出现一次的数字(II)
260. 只出现一次的数字 III - 力扣(LeetCode)
题目要求不能开额外空间,也就是限定哈希表不能使用,同时线性时间复杂度,也就是不能进行排
序算法.
一种较为简单的思路,就是将每一个数字,都看成二进制的形式,那对于一个数字来说,每一位不
是1就是0,对于出现3个相同数字来说,该位至少会出现3个1或者3个0以上,如果对所有数的相同
位统计1的个数,再取余,得到剩余的1,必定是只出现1的数所提供的.
class Solution {
public:
int singleNumber(vector<int>& nums) {
int anw = 0;
//一个int类型的数字总共有32位
for (size_t i = 0;i < 32;++i)
{
//记录每一位数字总共出现多少个1
int cnt = 0;
//遍历nums数组中的每一个数,统计每一位对应的1的个数有多少
for (auto e:nums)
cnt += ((e >> i) & 1);
//假如有多出来的1,说明该1必是从仅出现1次数字第i位所得到的
if (cnt % 3 == 1)
anw |= (1 << i);
}
return anw;
}
};
还有另外一种思路,不过需要一定的数电知识.
同样将每一个数字,都看成二进制的形式,求解不同的那个数字,我们可以将它看做解决三个相同
的数,怎么运算使其等于0.那剩下的那个数字就是唯一的数字.
对于这三个数的每一位来说,会有以下的转换表达式.
用0,1,2分别代表现在积累的1的个数,当遇到当前位是0,不会改变状态,当遇到当前位是1,则改
变状态.
但我们知道,单纯的0,1是无法表示三个状态,,因此我们用两位表示这三种状态.(00,01,10),11
状态舍弃不用.
那上面的状态图就可以表示为这样一种形式.
运用卡诺图,分别化简,得到新的ai,bi的表达式.
具体如何用卡诺图化简得到表达式,可以参考这篇文章.
(62条消息) 逻辑函数常用的描述方法及相互间的转化_逻辑函数的五种表示方法_·present·的博客-CSDN博客
将最后得到的结果,转换成程序即可.
PS:
1.当我们遍历完数组中的所有元素后,每一位要么是00,要么是01,所以结果返回b即可.
2.a.b需要同时更新,所以需要用临时变量辅助.
class Solution {
public:
int singleNumber(vector<int>& nums) {
int a = 0,b = 0;
for (auto e:nums)
{
int new_a = (~a & b & e) | (a & ~b & ~e);
int new_b = ~a & (b ^ e);
a = new_a;
b = new_b;
}
return b;
}
};
6.只出现一次的数字(III)
260. 只出现一次的数字 III - 力扣(LeetCode)
这道题难度有点大,不太好想,需要做过第一道题后,才可能会有一点思路.
关键是如何分别将只出现一次的数字分到两个组,具体步骤,可以看代码理解.
PS:条件判断需要注意加括号,有符号优先级问题存在.
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
//求出两个只出现1次的数的异或结果ret
int ret = 0;
for (auto e : nums)
{
ret^=e;
}
//由于两个只出现一次的数不同,则ret必定有一位是1
int index = 0;
while (((ret >> index) & 1) == 0)
{
index++;
}
//遍历数组,该位为1的分到一组,异或;反之该位为0的分到一组,异或
int num1 = 0,num2 = 0;
for (auto e :nums)
{
if (((e >> index) & 1) == 1)
{
num1 ^= e;
}
else
{
num2 ^= e;
}
}
vector<int> anw;
anw.push_back(num1);
anw.push_back(num2);
return anw;
}
};
7.电话号码的数字组合
这道题本身难度有点大,主要有几个难点:
第一,按键数字字数是不确定的,因此用循环来实现难度会比较大,至少难以确定循环多少层.
第二,返回类型是一个vector<string>的自定义类型变量,对于初学vector类的人来说,有时候比较
难理解其内部的实际结构是什么.
比较简单的一种想法是用递归去实现,由于题目提供的letterCombinations函数是用来返回答案
的,因此递归函数,我们要单独进行实现.
如何设置参数呢?设置多少个参数呢?这都是我们需要考虑的.
首先,肯定要传引用的vector<string>对象,还有层数level
其次,每一次递归,我们还需要一个String接收遍历得到的字符
最后,还有digits数组,不然我们怎么得到相应数字对应得字母串呢?
递归终止条件比较好确定,当层数和digits字母串大小相同的时候,比如说“23”,那此时层数已经为
2,就不需要再往下递归了,可以把String压入我们的vector<string>对象中.
class Solution {
//数字有相应的字母串,0和1不对应任何字母串
string numtoStr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
void Combinations(string CombineStr,size_t level,string digits,vector<string>&vv)
{
//假设层数已经达到相应的字符长度,递归停止
if (level == digits.size())
{
vv.push_back(CombineStr);
return;
}
//找出当前层所对应的数字
int num = digits[level] - '0';
//取出其对应的字符串
string str = numtoStr[num];
//递归遍历该字符串
for (auto ch: str)
{
Combinations(CombineStr + ch,level + 1,digits,vv);
}
}
vector<string> letterCombinations(string digits) {
vector<string> vv;
if (digits.size() == 0) return vv;
Combinations("",0,digits,vv);
return vv;
}
};