2023年3月1日
今天的任务有三个
一、数组理论基础 https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
i.数组的声明
Type_name Variable[ArraySize]; //[]内的值表示元素的个数,Type_name表示类型名字
int array[10];//定义了一个有10个元素的int型数组
ii. 数组的下标
数组的下标是从0开始的,Array[0]表示第一个元素,Array[1]表示第二个元素,以此类推。
int array[5]={1,2,3,4,5};
cout<<"第一个元素为:"<<array[0]<<"第二个元素为:"<<array[1]<<endl;
iii. 数组的初始化
当提供的值少于数组的元素数目时,编译器会将其余的值设置为0.
当数组初始化时,[]为空,C++会根据你写的内容自动计算元素个数
iv. C++11数组初始化方法
首先,数组初始化时,可以省略等号(=)
double arrays[4]{1.0,2.25,3.6,2.456}
其次,可以不在大括号内包含任何东西,这将把所有元素都设置为0
unsigned int count[10]=[]
float balance[5]{}
最后,列表初始化禁止缩窄转换
char test[]{'h',123,'\0'}//错误的!
v. C++STL中的Vector
首先介绍一下C++STL,容器是STL的基础,提供了存放数据元素的一系列模板,按照实现方式及特性定义了多种容器,不同的容器有着不同的底层数据结构与特点,适用于不同的应用。
本节介绍的是vector向量容器。
vector向量容器以类模板的形式提供的动态数组,与数组类似,vector的元素也存储在连续的空间中,支持使用下标快速访问元素。与数组不同的是,vector容器的大小可以动态改变,相关存储空间的申请与分配由容器自动完成。
首先是容器的构造:
#include<iostream>
#include<string>
#include<vector>
class TestClass{};
int main(){
string str[]={"wwc","zhl","zza"};
vector<int> v1;//空vector,元素类型为int
vector<int> v2(10);//包含10个int元素的vector
vector<float> v3(10,0)//10个float元素,初始值为0
vector<std::string> v4(str,str+2);//string初始化
vector<std::string>::iterator sIt=v4.begin();//设置迭代器sIt,一开始指向第一个元素
while(sIt != v4.end()) std::cout<<*sIt++<<"";
std::cout<<std::end;//清空缓冲区
vector<std::string> v5(v4);//vector对象直接赋值
for(int i=0;i<v5.size();i++) std::cout<<v5[i]<<"";
std::cout<<std::endl;
vector<TestClass> myC;
vector<TestClass*> pmyC;
std::cout<<"TestClass Success!"<<std::endl;
}
其次是vector容器的size、capacity、resize和max_size:
vector v;
v.capacity();//capacity表示预分配的存储空间总数
v.size();//表示容器中目前元素个数
v.max_size();//最大大小
v.resize();//改变容器大小
再者是vector的元素访问:
vector重载了operator[]运算符,使其可以像数组一样通过下标访问,起始值也是0
成员函数:
front():返回容器中第一个元素
last():返回容器中最后一个元素
at(size_type n):支持随机访问
同时支持迭代器
at和[]的区别在于,at会对下标值是否越界进行检查,若越界,则抛出out of range错误,而operator[]不会检查
然后就是重头戏,在vector中插入与删除元素
成员函数:
push_back():在尾部插入元素
pop_back():在尾部删除元素
insert():在向量内部插入
erase():删除元素
其中这个insert()用于在指定位置插入新元素,在插入元素后,vector的迭代器会失效,因为vector的存储地址发生了变化(重新申请了一个空间,类似于数组)
(1)insert(iterator position,const value_type& val);iterator position表示插入位置,const value_type& val表示待插入元素的引用,例如:
vector<int> v;
v.insert(v.begin(),8);//在v开始的地方插入8
(2)insert(iterator position,size_type n,const value_type& val):填充n个值相同的元素,其中size_type n表示元素个数
(3)利用迭代器插入指定范围内的元素值:
template<class T>
void insert(iterator positon, T first,T last);
其中T first表示待插入元素的起始位置,T last表示待插入元素的结束位置之后,即存储范围实际是[first,last)。
关于erase:
(1)删除指定位置的元素:iterator erase(iterator position)
(2) 删除[first,last)范围内的所有元素:iterator erase(iterator first, iterator last)
insert()向vector中特定位置插入元素将会影响容器的size和capacity,而erase仅会改变容器的size,capacity不会发生改变
最后是vector元素的赋值与交换
成员函数:
assign():用于向向量中分配新的元素以替换现有的元素,同时可修改vector的大小
(1)通过迭代器指明填充范围
template<class T>
void assign(T first,T last)
(2)填充n个特定值
void assign(size_type n,const value_type& val);
swap()用于互换两个类型相同但大小可以不同vector容器对象,其实现方法是直接互换两个vector的引用而非互相拷贝赋值,因此,在两个vector互换后,原有的迭代器依然有效,两个vector中的元素并没有发生任何赋值与交换,调用方法为v1.swap(v2),调用swap()之后,v1与v2的引用地址进行了交换,但容器内的元素仍然维持不变。
vi. C++STL中的array
哥们太累了打字,放个链接自己看把>,<
【STL】C++ STL之array详解_c++ stl array_行码棋的博客-CSDN博客
二、二分查找 https://leetcode.cn/problems/binary-search/
大二的时候学过数据结构,当时只知道思想,很少上手打过代码,这也是我想参加算法训练的原因。
经过简单的学习以后,关于整型相加溢出的问题可以看:int类型用c语言判断相加溢出_如何判断有符号整数加法溢出?_小子骚骚的博客-CSDN博客,需要计组的一点基础
二分查找的前提是有序数组,这点很重要!
二分查找的思想在算法里面就是分治思想,将大问题转化为小问题进行求解,最后得到f(1)
二分查找的关键点就是循环不变量,即区间,左闭右闭的判断和左闭右开的区间的判断是不一样的,在整个的循环过程中要严格按照循环不变量进行判断,判断left和right到底能不能相等,这样才能将问题考虑得更加全面和更加深入。
源码奉上(C++):
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right){//左闭右开
int mid = left + ((right - left) >> 1);
if(target < nums[mid] ){
right=mid;
}
else if(target > nums[mid]){
left = mid+1;
}
else{
return mid;
}
}
return -1;
}
};
三、移除元素 https://leetcode.cn/problems/remove-element/
这道题我一开始想的是,直接erase不就行了吗,源代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
vector<int>::iterator it = nums.begin();
while(it != nums.end()){
if(*it == val){
nums.erase(it);
it = nums.begin();
continue;
}
it++;
}
return nums.size();
}
};
erase本身能自动删除该位置的元素并将后面的元素进行移动,同时改变size的值,充分利用vector容器的成员函数,不过这种方法时间较长,内存占用较大,注意,erase并不会新创建一个空间进行赋值,而是直接将后面的数字自动往前挪,验证代码如下:
#include<vector>
#include<iostream>
int main(){
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
std::cout << &v[3]<<std::endl;
v.erase(v.end()-1);
std::cout << &v[3]<<std::endl;
system("pause");
return 0;
}
可以看到,两次的运行结果是一样的。
下面来分享文章给的三种方法:
暴力解法
这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
第一个for用来查找元素相等时候的位置,第二个for用来将后面的元素进行前移,比较好理解,不多做赘述,源代码如下:
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
快慢指针
这个方法的本质也是覆盖思想,只不过用的方法不一样。当两个指针都从左边开始,到右边结束的过程中,遇到要删除的元素的时候,一个指针原地不动,另一个往后移动,这种情况下,当下一次循环的时候,将往后移动的指针(快指针)对应的值赋值给原地不动的那个指针(慢指针)所指向的值,给人一种“赋值”操作的感觉,这种情况下是时间复杂度是O(n),比暴力解法更快,源代码如下:
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
双指针
这种方法是很多数组算法题经常用到的,两个指针一前一后,往中间靠拢,左边找跟要删除的值相同的值,右边的值找跟要删除的元素的值不同的值,然后将后面指针所指的数赋值给前面指针所指的数,形成“删除”的效果,最后左边指针所指的位置就是要返回的数组长度(就是把跟val值相同的元素都放到后面,把不同的值都放在前面,最后leftIndex一定指向了最终数组末尾的下一个元素),源代码如下:
/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
今天的分享到此结束了,希望能坚持到底吧,坚持不到那也没办法喽,明天见!