(续上文)
3.5 #include <set>
头文件set主要包括set和multiset两个容器,分别是“有序集合”和“有序多重集合”,即前者的元素不能重复,而后者可以包含若干个相等的元素。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同。
3.5.1 声明
1 | set<int> s; |
3.5.2 size/empty/clear
与vector类似
3.5.3 迭代器
set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和–“两个与算术相关的操作。
设it是一个迭代器,例如set<int>::iterator it;
若把it++
,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序的结果中,排在it下一名的元素。同理,若把it--
,则it将会指向排在“上一个”的元素。
3.5.4 begin/end
- 返回集合的首、尾迭代器,时间复杂度均为O(1)。
- s.begin() 是指向集合中最小元素的迭代器。
- s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个“前闭后开”的形式。因此–s.end()是指向集合中最大元素的迭代器。
3.5.6 insert
- s.insert(x)把一个元素x插入到集合s中,时间复杂度为O($logn$)。
- 在set中,若元素已存在,则不会重复插入该元素,对集合的状态无影响。
3.5.7 find
s.find(x) 在集合s中查找等于x的元素,并返回指向该元素的迭代器。若不存在,则返回s.end()。时间复杂度为O($logn$)。
3.5.8 lower_bound/upper_bound
- 这两个函数的用法与find类似,但查找的条件略有不同,时间复杂度为 O($logn$)。
- s.lower_bound(x) 查找大于等于x的元素中最小的一个,并返回指向该元素的迭代器。
- s.upper_bound(x) 查找大于x的元素中最小的一个,并返回指向该元素的迭代器。
3.5.9 erase
- 设it是一个迭代器,s.erase(it) 从s中删除迭代器it指向的元素,时间复杂度为O$logn$)
- 设x是一个元素,s.erase(x) 从s中删除所有等于x的元素,时间复杂度为O(k+$logn$),其中k是被删除的元素个数。
3.5.10 count
s.count(x) 返回集合s中等于x的元素个数,时间复杂度为 O(k +$logn$),其中k为元素x的个数。
3.5.11 实例
1 |
|
3.6 #include <map>
map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。Map的key和value可以是任意类型,其中key必须定义小于号运算符。
3.6.1 声明
map<key_type, value_type> name;
例如:
map<long, long, bool> vis;
map<string, int> hash;
map<pair<int, int>, vector<int>> test;
3.6.2
size/empty/clear/begin/end均与set类似。
3.6.3 Insert/erase
与set类似,但其参数均是pair<key_type, value_type>
。
3.6.4 find
h.find(x) 在变量名为h的map中查找key为x的二元组。
3.6.5 []操作符
h[key] 返回key映射的value的引用,时间复杂度为O(logn)。
[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。
3.6.6 实例
1 |
|
3.7 补充的几个STL
1 |
|
3.8 二元组(pair)
pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。
1 |
|
4.位运算与常见库函数
C++帮我们实现好了很多有用的函数,我们要避免重复造轮子。 ——yxc
4.1 位运算
其实是数学上的概念。
符号 | 运算 | |
---|---|---|
& | 与(AND) | |
\ | 或(OR) | |
~ | 非(NOT) | |
^ | 异或(XOR)(不进位的加) | |
>> |
右移 | |
<< |
左移 |
1 | 0&0 = 0, 0&1 = 0, 1&0 = 0, 1&1 = 1 |
总之,在C中,左移是逻辑/算术左移(两者完全相同),右移是算术右移,会保持符号位不变.实际应用中可以根据情况用左/右移做快速的乘/除运算,这样会比循环效率高很多.
常用操作:
(1) 求x的第k位数字 x >> k & 1
(2) lowbit(x) = x & -x=x & (~x + 1),返回x的最后一位1及以后的数
比如12(10)二进制表示为 1100(2),lowbit(12) == 100(2)== 4(10)
。
(3) 我们可以直观的发现,如果是一个偶数^1,那么答案是偶数+1.如果是一个奇数^1,那么答案是奇数-1。
1 |
|
5.acwing.67.数字在排序数组中出现的次数
题目描述
求一个排好序的数组中k的个数。例如输入排序数组[1, 2, 3, 3, 3, 3, 4, 5]和数字3,由于3在这个数组中出现了4次,因此输出4。
解题思路
目的是练习stl和常用的库函数
题解一:
使用有序多重集合multiset
1 | class Solution { |
题解二:
遍历vector,计数
1 | class Solution { |
题解三:
二分做法,使用lower_bound和upper_bound,指针运算得出次数
1 | class Solution { |
作者:醉生梦死
链接:https://www.acwing.com/solution/content/16094/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于vector中的lower_bound和upper_bound
共同点
比较的“首”地址+ 一个数组元素的地址(对应的这个数组里边任意一个元素的地址,表示这个二分里边的比较的”结尾’地址)+ 你要二分查找的那个数。
一个数组元素的地址(或者数组名来表示这个数组的首地址,用来表示这个数组的开头比较的元素的地址,不一定要是首地址,只是用于比较的“首”地址)+ 一个数组元素的地址(对应的这个数组里边任意一个元素的地址,表示这个二分里边的比较的”结尾’地址)+ 你要二分查找的那个数。
例如:
1 | lower_bound(r[x].begin(),r[x].end(),l) |
区别
lower_bound
返回第一个大于等于x的数的地址
upper_bound
返回第一个大于x的数的地址
6.acwing.68.数组中唯一只出现一次的数字
1 | // solution 1,我的题解 |
7.acwing.32. 调整数组顺序使奇数位于偶数前面
主要是不会交换vector元素的位置。思路还是知道的(双指针)。不要用迭代器做!
输入一个整数数组,实现一个函数来调整该数组中数字的顺序。
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
样例
1 | 输入:[1,2,3,4,5] |
算法
(双指针扫描) O(n)
用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。
每次迭代时需要进行的操作:
第一个指针一直往后走,直到遇到第一个偶数为止;
第二个指针一直往前走,直到遇到第一个奇数为止;
交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;
时间复杂度
当两个指针相遇时,走过的总路程长度是 n,所以时间复杂度是 $O(n)$。
1 | class Solution { |
8.acwing.17.从尾到头打印链表
输入一个链表的头结点,按照 从尾到头 的顺序返回节点的值。
返回的结果用数组存储。
1 | // solution 1,我的题解 |