第一章 语法基础
part1 输入输出 printf&scanf cin&cout
01. scanf && printf
- //int型
int a,b; scanf("%d %d",&a,&b); printf("%d, %d\n",a,b);
- double型
double a,b; scanf("&lf %lf",&a,&b); //输入:3 5 printf("&.2lf %.3lf",a,b); //输出:3.00 5.000 (会自动 四舍五入)
- char型
char c1,c2; scanf("%c %c",&c1,&c2);//输入:a b printf("%c %c",c1,c2); //输出:ab
- char数组型
char s[10]; scanf("&s", s); //输入:Hello World (注意这里 直接是 s 不用取地址 s直接表示的是指针) printf("%s",s); //输出:Hello (注意:%s输入遇到 空格 '\0' 或者 回车 '\n'就会停下) //如果想要 全部一行输入的话 //使用正则表达式 '^'表示非 [^\n]表示 非回车'\n'不结束scanf 即读入换行符就结束读入 scanf("%[^\n]",s);//输入:Hello World printf("%s",s);//输出:Hello World
类型 对应标识符 int %d double %lf long long %lld char %c char[] %s
02 cin && cout
可自动判断变量类型
double 和 int型
double a,b; cin>>a>>b; //输入:23 cout<<a<<' '<<b<<'\n'; //输出:2 3 //如果想要保留小数位数 cout<< fixed << setprecision(3) <<a<<' '<<b<<'\n'; //输出:2.000 3.000
char型
char ch; cin>>ch;//输入:ab cout<<ch;//输出: a
char[]型
char s[10]; cin>>s;//输入:lan qiao cout<<s;//输出:lan (注意:cin输入字符也 会遇到空格和回车就结束)
string型
include<string> string s; gerline(cin,s);//输入:lan qiao (即整行输入) cout<<s;//输出:lan qiao
03 取消同步流
由于cin和cout自动判别类型等,会使得效率降低,可通过
取消同步流
的方法,提高效率int main() { ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); int x.......//后面省略 }
ios::sync_with_stdio(0)
:
ios::sync_with_stdio
是一个成员函数,用于设置C++的输入输出流与C标准I/O流(stdio)的同步。- 传入参数0表示取消同步,允许输入输出流在多线程中并行操作。
cin.tie(0)
和cout.tie(0)
:
cin.tie
和cout.tie
分别是用于指定输入和输出流的关联流指针的成员函数。- 传入参数0表示取消关联,使得输入和输出流不再被关联在一起。
- 取消关联后,输入和输出可以在多个线程中独立操作,而不受彼此影响。
tips:头文件可以尝试使用
#include<bits/stdc++.h>//用于一次性包含大量的C++标准头文件,比如 <iostream>, <vector>, <algorithm>, <string> 等等。
using namespace std;
part2 string的基本操作
- 初始化
#include<iostream> #include<string> using namespace std; int main(){ //几种简单的初始化 string str1; //str1为"" string str2="Hello World"; string str3(5,'A'); //string(个数,字符) 即这里表示"AAAAA" //取子串 string str4=str2.substr(0,5);// substr(起始位置,长度) 这里即表示 "Hello" }
- 各项基本操作
//1、获取字符串长度 length / size string str1 = "Hello, World"; int length1 = str1.length(); int length2 = str1.size(); //2、拼接字符串 + / append string str1 = "Hello"; string str2 = "world"; string result1 = str1 + ", " + str2; string result2 = str1.append(", ").append(str2); //3、字符串查找 find string str1 = "Hello, World";//下标从0开始 size_t pos = str.find("World"); if(pos != -1){ cout << "Position is :" << pos << endl; // 如果找到了 就返回pos 即第一个元素的下标 7 } else{ cout << "Not found" << endl; } //4、字符串替换 replace string str1 = "Hello, World!!"; str1.replace(7,5,"Universe");//replace(要替换的子串在原字符串的位置,要替换的子串在原字符串的长度,新子串) 即结果是“Hello, Universe!!” //5、提取子字符串 substr 注意不要越界 string str1 = "Hello, World!!"; string substr=str1.substr(0,5);// substr(起始位置,长度) 这里即表示 "Hello" //6、字符串比较 compare / 也可以使用 > < == >= <=来比较 (按字典序来比较) string str1 = "Hello"; string str2 = "World"; int result = str1.compare(str2); //7、字符串反转 reverse(str.begin(),str.begin()+5); //8、字符交换 swap(str[i],str[str.length()-i]);
- 遍历方式
//1、循环枚举下标 for(int i=0;i<s.length();i++){ ..... } //2、auto枚举(其中&表示取引用类型,如对i修改会改变原来的值) for(auto i : s){ ......//此处修改无效,不改变s的值 } for(auto &i : s){ ......//此处修改会改变s的值 }
Part3 char的基本操作
一下操作 在头文件中
- 大小写判断 islower / isupper(返回bool类型)
char ch1='A'; char ch2='B'; if(islower(ch1)){ //判断是否小写 .... } if(isupper(ch2)){ //判断是否大写 ...... }
- 大小写转化 tolower / toupper
char lowercaseCh1 = tolower(ch1); char uppercaseCh2 = toupper(ch2);
- Ascii码 (略)
Part4 排序-----sort函数
sort函数使用的是快速排序,时间复杂度为 O ( n l o g 2 ( n ) ) O(nlog_2(n)) O(nlog2(n)),定义在中
sort(起始地址,结束地址的下一位,*比较函数); //实例1 vector<int> v={5,1,3,9,11}; sort(v.begin(),v.end()); // 结果为 1 3 5 9 11(默认为升序) //第三个参数:比较函数 可以是函数或lambda表达式 //方法一:第一种 函数法 bool cmp(const int &u,const int &v) { return u>v; } sort(v.begin(),v.end(),cmp); // 结果为 11 9 5 3 1 /* 逻辑 v[i] v[j] 传入cmp中 比较大小 最后以 > > > 的 形式排序(即降序排列) */ //方法二:第二种 lambda表达式(匿名函数) sort(v.begin(),v.end(),[](const int &u,const int &v){return u>v;}); //方法三:使用c++中现成的升序和降序模版 sort(v.begin(),v.end(),less<data_type>());//升序 sort(v.begin(),v.end(),greater<data_type>());//降序 //实例 sort(v.begin(),v.end(),greater<int>()); // 结果为 11 9 5 3 1
一些输出的小技巧
//例如要求将 v 升序输出 再降序输出 #include<bits/stdc++.h> int main() { ....//此处省略读入到v中的过程 sort(v.begin(),v.end(),less<int>()); for(int i=0;i<n;i++) cout << v[i] << " \n"[i==(n-1)]; //*****这里当i==(n-1)时候,输出'\n' }
Part5 二分查找
二分查找的前提:只能对数组进行二分查找,且要求数组是单调的
binary_search( ) 函数 :通过二分查找算法来确定序列中是否存在目标元素,返回 bool值
vector<int> v = {1,3,5,7,9}; int target = 5; bool found = binary_search(v.begin(),v.end(),target);
lower_bound( ) & upper_bound( ) 函数 :通过二分查找,获取找到的元素的位置
要求:默认情况下,数组要求升序才可使用,可以通过比较函数(或者用sort)实现降序数组的二分查找
lower_bound(v.start(), v.end(), target);//返回地址[st, ed)中第一个 ≥target的元素的地址。 upper_bound(v.start(), v.end(), target);//返回地址[st, ed)中第一个 >target的元素的地址。 //如果不存在则返回最后一个元素的下一个位置,在vector中即end()。
Part6 全排列函数
next_permutation()函数
next_permutation函数用于生成当前序列的下一个排列。它按照字典序对序列进行重新排
列,如果存在下一个排列,则将当前序列更改为下一个排列,并返回true;如果当前序列已
经是最后一个排列,则将序列更改为第一个排列,并返回false。vector<int> nums={1,2,3}; while(next_permutation(nums.begin(),nums.end())){ .....// }
prev_permutation()函数
prev_permutation函数与next_permutation函数相反,它用于生成当前序列的上一个排列。它
按照字典序对序列进行重新排列,如果存在上一个排列,则将当前序列更改为上一个排列,并返回
true;如果当前序列已经是第一个排列,则将序列更改为最后一个排列,并返回false。vector<int> nums={3,2,1}; while(prev_permutation(nums.begin(),nums.end())){ .....// }
Part7 最值函数
min() && max()
min(a,b) 返回a和b中较小的那个值,只能传入两个值,或传入一个列表。
max(a,b) 返回a和b中较大的那个值,只能传入两个值,或传入一个列表。
x = min(3,5);// x=3 x = max(3,5);// x=5 x = min({1,2,3,4});//x=1 x = max({1,2,3,4});//x=4
min_element() && max_element
min_element(st, ed)返回地址[st, ed)中最小的那个值的地址(迭代器),传入参数为两个地址或迭代器。
max_element(st,ed)返回地址[st, ed)中最大的那个值的地址(迭代器),传入参数为两个地址或迭代器。vector<int> v = {5,1,3,9,11}; cout << *max_element(v.begin(),v.end()) << '\n'; //输出最大元素,*表示解引用,即通过地址(迭代器)获得值
nth_element()
nth_element(st,k,ed) 进行部分排序,返回值为void()
传入参数为三个地址或迭代器。其中第二个参数位置的元素将处于正确位置,其他位置元素的顺序可能是任意的,但前面的都比它小,后面的都比它大。
时间复杂度O(n)。vector<int> v = {5,1,7,3,10,18,9}; nth_element(v.begin(),v.begin()+3,v.end());//输出:3 1 5 7 9 18 10 //相当于能找出该数组中第n大的元素
Part8 其他的库函数
- memset()
定义在头文件中,用于设置内存块值的函数
void* memset(void* ptr, int value, size_t num): /* 1.ptr:指向要设置值的内存块的指针。 2.value:要设置的值,通常是一个整数。(8位二进制数) 3.num:要设置的字节数。 memset()函数将ptr指向的内存块的前num个字节设置为value的值。它返回一个指向ptr的指针。 memset()函数通常用于初始化内存块,将其设置为特定的值。 */ //实例 #include<bits/stdc++.h> using namespace std; int main(){ int a[5]; memset(a,0,sizeof a); //or 可以写成 memset(a,0,sizeof(a)); 只有赋值0或-1时才能正确赋值 }
- swap()
swap(T &a,T &b); /* 1.a:要交换值的第一个变量的引用 2.b:要交换值的第二个变量的引用。 swap()函数通过将第一个变量的值存储到临时变量中,然后将第 二个变量的值赋给第一个变量,最后将临时变量的值赋给第二 个变量,实现两个变量值的交换。 swap()函数可以用于交换任意类型的变量,包括基本类型(如整 数、浮点数等)和自定义类型(如结构体、类对象等)。 以下是一个示例,展示如何使用swap()函数交换两个整数的值: */ //实例 int a=20; int b=10; swap(a,b);
- reverse()
用于反转容器中元素顺序的函数,定义在
void reverse(BidirIt first,BidirIt last); /* reverse()函数接受两个参数: 1.first:指向容器中要反转的第一个元素的迭代器。 2.last:指向容器中要反转的最后一个元素的下一个位置的迭代器。 reverse()函数将[first, last)范围内的元素顺序进行反转。 注意左闭右开 也就是说,它会将[first,last)范围内的元素按相反的顺序重新排列。 reverse()函数可用于反转各种类型的容器,包括数组、向量、链表等。 */ //实例 #include <iostream> #include <vector> #include <algorithm> using namespace std int main(){ vector<int> vec = {1,2,3,4,5}; reverse(vec.begin(),vec.end()); }
- unique()
用于去除容器中相邻重复元素的函数,定义在
Forward unique(ForwardIt first,ForwardIt last); /* unique(first,last)函数接受两个参数: 1.first:指向容器中要去重的第一个元素的迭代器。 2.last:指向容器中要去重的最后一个元素的下一个位置的迭代器。 unique()函数将[first, last)范围内的相邻重复元素去除,并返回一个指向去重后范围的尾后迭代器。 去重后的范围中只保留了第一个出现的元素,后续重复的元素都被移除。 unique()函数可用于去除各种类型的容器中的相邻重复元素,包括数组、向量、链表等。 */ //实例 //以下是一个示例,展示如何使用unique()函数去除一个整型向量中的相邻重复元素: int main(){ vector<int> vec={1,1,2,2,3,3,3,4,4,5}; auro it=unique(vec.begin(),vec.end()); vec.erase(it,vec.end()); }
vec的起始状态
1 1 2 2 3 3 3 4 4 5 vec.begin() vec.end() 经过 unique 后的状态
1 2 3 4 5 1 2 3 3 5 vec.begin() it vec.end() 可以看出unique处理完后 返回值为一个指向去重后范围的尾后迭代器
一般要求在排序后使用。
part9 STL
vector
vector的定义和特性
在C++中,vector是一个动态数组容器,可以存储一系列相同类型的元素。它是标准库中定义的模板类。
容器大小:vector是一个动态数组,可以根据需要自动调整大小。它会根据元素的数量动态分配内存空间。
元素访问:可以通过索引来访问vector中的元素。索引从0开始,最后一个元素的索引是size()-1。可以使用[ ]运算符或at()函数来访问元素。(这里的size是uint类型,如果元素为空那么会非常大,需要强制转化(int))
元素添加和删除:可以使用**push_back()函数在vector的末尾添加元素,使用pop_back()函数删除末尾的元素。还可以使用insert()函数在指定位置插入元素,使用erase()**函数删除指定位置的元素。
容器大小管理:可以使用size()函数获取vector中元素的数量,使用**empty()函数检查vector是否为空。还可以使用resize()**函数调整vector的大小。
迭代器:vector提供了迭代器,可以用于遍历容器中的元素。可以使用begin()函数获取一个元素的迭代器,使用end()函数获取指向最后一个元素之后位置的迭代器。vector的常用函数
vec.push_back(const T& value);//将元素添加到vector的末尾 vec.pop_back(const T& value);//删除vector末尾的元素(一定保证vector是非空的) vec.begin();//返回指向vector第一个元素位置的迭代器 vec.end();//返回指向vector最后一个元素之后位置的迭代器 sort(vec.begin(),vec.end(),greater<data_type>());
vector排序去重
sort(vec.begin(),vec.end()); auto last = unique(vec.begin(),vec.end()); vec.erase(last,vec.end());
set
set集合
set是一种容器,用于存储一组唯一的元素,并按照一定的排序规则进行排序。set中的元素是默认按照升序排序的,默认情况下,它使用元素的比较运算符(<)来进行排序。
set的内部实现使用了红黑树(一种自平衡的二叉搜索树)来存储元素,并保持元素的有序性。
这使得在set中插入、删除和查找元素的时间复杂度都是对数时间,即O(logn)。
set中的元素是唯一的,即不允许重复的元素存在。当插入一个重复的元素时,set会自动忽略该元素。set<int,greater<int>> mySet;
multiset多重集合
multiset是一种容器,它与set类似,用于存储一组元素,并按照一定的排序规则进行排序。
不同之处在于,multiset容器允许存储重复的元素。unordered_set无序集合
unordered set是一种容器,用于存储一组唯一的元素,并且没有特定的顺序。 unordered set容器使用哈希表来实现元素的存储和访问,因此元素的插入、删除和查找的时间复杂度都是常数时间,即O(1)。
stack栈
stack的定义和结构
stack是一种后进先出(LIFO)的数据结构,使用前需要包含头文件
stack的常用函数
queue队列
queue队列
queue是一种先进先出(FIFO)的数据结构
- priority_queue优先队列(大顶堆或小顶堆)
priority_queue与普通队列不同,priority_queue中的元素是按照一定的优先级进行排序的
默认情况下,priority_queue按照元素的值从大到小进行排序,即最大元素位于队列的前面那么在定义时即存在比较函数
priority_queue<int,vector<int>,greater<int>()> pq; //greater<data_type> 存在于<function>头文件中
deque双端队列
deque(双端队列)是一种容器,它允许在两端进行高效的插入和删除操作。deque是由一系列连续的存储块(缓冲区)组成的,每个存储块都存储了多个元素。这使得deque能够在两端进行快速的插入和删除操作,而不需要移动其他元素。
pair
pair的定义和结构
在C++中,pair是一个模版类,用于一对值的组合,在头文件中。
pair类有两个成员变量,first和second,分别表示第一个值和第二个值。
pair<int,double> p1(1,3.14); pair<char,string> p2('a','Hello');
pair的嵌套
pair可以进行嵌套,也就是说可以将一个pair对象作为另一个pair对象的成员。
通过嵌套pair,你可以方便地组合多个值,并形成更复杂的数据结构。pair<int,pair<int,int>> p1(3,make_pair(4,5));
pair自带排序规则
pair自带的排序规则是按照first成员进行升序排序。如果first成员相等,则按照second成员进行升序排序。这意味着当你使用标准库中的排序算法(如std:sort)对包含pair对象的容器进行排序时,会根据pair对象的first成员进行排序。
map
map
map是一种关联容器,用于存储一组键值对<key,value>,其中每个键(key)都是唯一的。
map容器根据键来自动进行排序,并且可以通过键快速查找对应的值。
map容器使用红黑树(Red-Black Tree)数据结构来实现,具有较快的插入、删除和查找操作的时间复杂度O(logn)。multimap
multimap是一种关联容器,类似于map,但允许存储多个具有相同键的键值对。
unordered_map
unordered_map是一种关联容器,用于存储一组键值对<key,value>,其中每个键(key)都是唯一的。
与map和multimap不同,unordered_map不会根据键的顺序进行排序,而是使用哈希函数将键映射到存储桶中。
这使得unordered_map具有更快的插入、删除和查找操作的时间复杂度,但不保证元素的顺序。unordered_map拥有极好的平均时间复杂度和极差的最坏时间复杂度,所以他的时间复杂度是不稳定的。一般情况下我们更愿意使用复杂度稳定的map而不是unordered_map。
list
list的定义和结构
list的使用频率不高,在做题时极少遇到需要使用list的情景。
list是一种双向链表容器,它是标准模板库(STL)提供的一种序列容器。list容器以节点(node)的形式存储元素,并使用指针将这些节点链接在一起,形成一个链表结构。双向性:每个节点都包含指向前一个节点和后一个节点的指针,因此可以在常数时间内在链表中的任意位置进行插入、删除和访问操作。
动态大小:链表的大小可以根据需要动态扩展或收缩,不需要预先指定容器的大小。
不连续存储:链表中的节点可以在内存中的任意位置分布,不要求连续存储,因此插入和删除操作不会导致元素的移动。list的常用函数
题目
1.小明和完美序列
问题描述
小明又新学了一个概念,叫做完美序列。一个仅包含数字序列被称为完美序列,当且仅当数字序列中每个数字出现的次数等于这个数字。比如(1),(2,2,3,3,3)。空序列也算。现在小明得到了一个数字序列,他想知道最少要删除多少个数字才能使得这个数字序列成为一个完美序列。输入格式:
输入包括两行。第一行一个整数n,表示数字序列中数字的个数。第二行,包括n个整数,是数字序列中具体的每个数字。
输出格式:输出一个整数,表示最少要删除的数字个数。
#include <bits/stdc++.h> using namespace std; int main() { map<int,int> myMap; int n; cin>>n; for(int i=0,j;i<n;i++) { cin>>j; myMap[j]++; } int ans=0; for(auto [x,y]:myMap) { if(y>x) ans+=y-x; else if(y<x) ans+=y; } cout<<ans<<endl; return 0; }