文章目录
- unordered系列关联式容器
- unordered_map
- unordered_map的文档介绍
- unordered_map的构造
- 接口使用:
- unordered_multimap
- unorder_map&&unorder_multimap对比:
- unordered_set
- unordered_set的文档介绍
- unordered_set的构造
- 接口使用
- unordered_multiset
- OJ练习
- 961.在长度2N的数组中找出重复N次的元素
- 349. 两个数组的交集
- 350. 两个数组的交集 II
- 217. 存在重复元素
- 884. 两句话中的不常见单词
- 总结:
- map/set与unordered_map/unordered_set的区别
unordered系列关联式容器
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 ,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想,最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同
unordered_map
unordered_map的文档介绍
http://www.cplusplus.com/reference/unordered_map/unordered_map/?kw=unordered_map
- unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value
- 在unordered_map中,键值通常用于唯一地标识元素,不允许重复键值,而映射值是一个对象,其内容与此键关联键和映射值的类型可能不同
- 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低
- unordered_maps实现了直接访问操作符**(operator[]),**它允许使用key作为参数直接访问value
- 它的迭代器至少是前向迭代器
需要引用头文件:#include<unordered_map>
unordered_map的构造
- 直接构造一个空容器
- 拷贝构造
- 使用迭代器构造
void test_UnMap()
{
unordered_map<int, int> um1;//构造一个键值对为<int,int>的空容器
unordered_map<int, int> um2(um1);//用um1拷贝构造um2
unordered_map<int, int> um3(um2begin(), um2.end());//使用迭代器拷贝构造um3
//构造的时候可以顺便初始化
unordered_map<int, int> um4{ {1,1},{2,2} ,{3,3} };//um4相当于有3个成员
}
接口使用:
常用接口:
函数声明 | 功能介绍 |
---|---|
容量相关: | |
bool empty() const | 检测unordered_map是否为空 |
size_t size() const | 获取unordered_map的有效元素个数 |
迭代器:注意:没有反向迭代器 | |
begin() | 返回unordered_map第一个元素的迭代器 |
end() | 返回unordered_map最后一个元素下一个位置的迭代器 |
cbegin() | 返回unordered_map第一个元素的const迭代器 |
cend() | 返回unordered_map最后一个元素下一个位置的const迭代器 |
元素访问: | |
operator[] | 返回与key对应的value,没有一个默认值 |
查询 | |
iterator find(const K& key) | 返回key在哈希桶中的位置,返回该位置的迭代器 |
size_t count(const K& key) | 返回哈希桶中键为key的键值对的个数 |
修改 | |
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 可以按key删除,也可以按迭代器删除,也可以删除一段迭代器区间 |
void clear() | 清空容器中有效元素个数 |
void swap(unordered_map&) | 交换两个容器中的元素 |
桶操作 | |
size_t bucket_count() const | 返回哈希桶中桶的总个数 |
size_t bucket_size(size_t n) const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
针对operator[]需要注意的是:
opeartor[key]:
注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,将key对应的value返回
- 若当前容器中已有键值为key的键值对,则返回该键值对value的引用
- 若当前容器中没有键值为key的键值对,则先插入键值对
<key, value()>
,然后再返回该键值对中value的引用
针对count函数:unordered_map中key是不能重复的,因此count函数的返回值最大为1
上述函数可以归结为:
成员函数 | 功能 |
---|---|
insert | 插入键值对,可以通过{key,value} make_pair(key,value) 构造匿名函数, 或者pair<key,value> 插入 |
erase | 删除指定key值的键值对,可以按值删除,也可以按迭代器删除,也可以删除一段迭代器区间 |
find | 查找指定key值的键值对,找到了返回该位置的迭代器,找不到返回end()位置的迭代器 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器的内容 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定key值的元素个数 |
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
实例:
#include<iostream>
#include<unordered_map>
using namespace std;
int main()
{
unordered_map<string, string> um;//key:string value:string
//1.插入
/*可以插入的方式:
1.pair插入
2.make_pair函数模板构造匿名函数插入
3.使用{}插入
4.[]插入 ->底层是调用insert.如果有就不处理
*/
um.insert(pair<string, string>("sort", "排序"));
um.insert(make_pair("Mango", "芒果"));
um.insert({ "Lemon","柠檬" }); //c++11的写法
um["Orange"] = "橙子";
um["Orange"] = "橙色";//只能有一个key值,不允许重复
//2.范围for遍历
for (auto& km : um)
{
//km是um的成员,是pair对象
cout << km.first << " " << km.second << endl;
}//sort 排序 Mango 芒果 Lemon 柠檬 Orange 橙色
cout << endl;
//3.迭代器遍历
unordered_map<string, string>::iterator it = um.begin();
//推荐直接使用auto auto it = um.begin();
while (it != um.end())
{
//it是迭代器,调用operator->得到pair对象的指针,再通过指针访问成员,优化为迭代器->成员
cout << it->first << " " << it->second << endl;
it++;
}//sort 排序 Mango 芒果 Lemon 柠檬 Orange 橙色
cout << endl;
//4.删除erase
//case1:按key键值删除
um.erase("Orange");//删除键值为Orange的元素
//case2:按迭代器删除->常搭配find函数使用,先找到了再删除
auto pos = um.find("sort");//找到键值为sort的元素
if (pos != um.end())
{
um.erase(pos);//找到了,就删除
}
for (auto& kv : um)
{
cout << kv.first << " " << kv.second << endl;
}//Mango 芒果 Lemon 柠檬
cout << endl;
//5.修改键对应的内容
//case1:通过find找到元素,然后再通过迭代器修改
pos = um.find("Lemon");
if (pos != um.end())
{
pos->second = "大柠檬";//找到了就修改
}
//case2:利用[]进行修改
um["Mango"] = "大芒果";
for (auto& kv : um)
{
cout << kv.first << " " << kv.second << endl;
}//Mango 大芒果 Lemon 大柠檬
cout << endl;
//6.容器相关
//count:统计键值为key的出现多少次
//size:容器的有效元素个数
//clear:清空容器
//empty:判断容器是否为空
//swap:交换两个容器的内容
cout << um.count("Mango") << endl;//1
cout << um.count("sort") << endl;//0
cout << um.size() << endl;//2
um.clear();
unordered_map<string, string> tmp{ {"test","测试"},{"Run","运行"} };
um.swap(tmp);
for (auto& kv : um)
{
cout << kv.first << " " << kv.second << endl;
} //test 测试 Run 运行
cout << endl;
return 0;
}
unordered_multimap
unordered_multimap容器与unordered_map容器的底层数据结构是一样的,都是哈希表
其次,它们所提供的成员函数的接口都是基本一致的,这两种容器唯一的区别就是,unordered_multimap容器允许键值冗余,即unordered_multimap容器当中存储的键值对的key值是可以重复的
举例子:
#include<string>
#include<iostream>
#include<unordered_map>
using namespace std;
int main()
{
unordered_multimap<string, string> umm;
umm.insert(make_pair("sort", "排序"));
umm.insert({ "Run","运行" });
umm.insert(pair<string, string>("run", "跑步"));
for (auto& km : umm)
{
cout << km.first << " " << km.second << endl;
}//sort 排序 run 跑步 Run 运行
cout << endl;
return 0;
}
unorder_map&&unorder_multimap对比:
由于unordered_multimap容器允许键值对的数据冗余,所以find函数和count函数返回的值就有不同的含义
成员函数find | 功能 |
---|---|
unordered_map | 返回键值为key的键值对应的迭代器 |
unordered_multimap | 返回底层哈希表中第一个找到的键值为key的键值对的迭代器 |
举例子:
int main()
{
unordered_multimap<string, string> umm;
umm.insert({ "world","世界" });
umm.insert({ "test","测试1" });
umm.insert({ "Hello","你好" });
umm.insert({ "test","测试2" });
umm.insert({ "test","测试" });
unordered_multimap<string, string>::iterator pos = umm.find("test");//找键为key对应的元素
//返回的是第一个找到的键值为key的键值对的迭代器
if (pos != umm.end())
{
cout << pos->first << " " << pos->second << endl;
} //test 测试1
cout << endl;
return 0;
}
成员函数count | 功能 |
---|---|
unordered_map容器 | 键值为key的键值对存在则返回1,不存在则返回0(find成员函数可替代,find函数找到了,说明存在键为key的元素) |
unordered_multimap容器 | 返回键值为key的键值对的个数(find成员函数不可替代) |
int main()
{
unordered_multimap<string, string> umm;
umm.insert({ "world","世界" });
umm.insert({ "test","测试1" });
umm.insert({ "Hello","你好" });
umm.insert({ "test","测试2" });
umm.insert({ "test","测试" });
cout << umm.count("test") << endl; //3
return 0;
}
注意:unordered_multimap容器不支持operator[]重载
- 原因: 如果允许键值对的键值冗余,调用
operator[]
运算符重载函数时,返回时存在歧义,因为不知道返回键值为key的哪个元素
unordered_set
unordered_set的文档介绍
http://www.cplusplus.com/reference/unordered_set/unordered_set/?kw=unordered_set
- unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素
- 在unordered_set中,元素的值同时也是唯一地标识它的key ,unordered_set有去重操作!!插入相同的key不起作用 unordered_set可以认为是<key,key>类型的键值对
- 在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key, unordered_set将相同哈希值的键值放在相同的桶中
- unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低
- 它的迭代器至少是前向迭代器
unordered_set的构造
- 直接构造一个空容器
- 拷贝构造
- 使用迭代器构造
void test_UnSet()
{
unordered_set<int> us1; //直接构造一个空容器
unordered_set<int> us2;//用us1拷贝构造us2
string tmp("Mango");
unordered_set<char> us3(tmp.begin(), tmp.end());//用迭代器拷贝构造us3
//同样可以构造的时候直接初始化
unordered_set<int> us4{ 1,2,3}; //内含3个成员
//也可以写成:
unordered_set<int> us5{ {1},{2},{3} };//内含3个成员
}
接口使用
这里直接给出常用的成员函数
成员函数 | 功能 |
---|---|
insert | 插入指定元素 |
erase | 删除指定元素,可以按值删除,也可以按迭代器删除,也可以按迭代器区间删除 |
find | 查找指定元素,找到了返回该位置的迭代器,如果找不到,返回end()迭代器位置 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器的内容 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定元素值的元素个数 |
迭代器相关函数: 注意unorderde_set也没有反向迭代器!
成员函数 | 功能 |
---|---|
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
例子:
#include<unordered_set>
int main()
{
unordered_set<char> us;
//1.插入元素 (会去重)
us.insert('a');
us.insert('a');
us.insert('z');
us.insert('x');
us.insert('y');
us.insert('z');
//2.遍历:迭代器 /范围for
unordered_set<char>::iterator it = us.begin();
//可以直接使用auto推导类型更方便 auto it = us.begin()
while (it != us.end())
{
cout << *it << " ";
it++;
} //y a z x
cout << endl;
for (auto& e : us) //e就是us的每一个成员
{
cout << e << " ";
} //y a z x
cout << endl;
//3.删除元素 ->常搭配find函数使用
us.erase('z');//按值删除
auto pos = us.find('a');
us.erase(pos);//按迭代器位置删除
for (auto& e : us) //e就是us的每一个成员
{
cout << e << " ";
}//y x
cout << endl;
//统计容器中值为y的元素个数
cout << us.count('y') << endl;//1
//返回容器的大小
cout << us.size() << endl;//2
//清空容器的内容
us.clear();
//判断容器是否为空
cout << us.empty() << endl;//1
//交换两个容器的内容
unordered_set<char> tmp{ 'a','b','c' };
us.swap(tmp);
for (auto& e : us)
{
cout << e << " ";
}//a b c
cout << endl;
return 0;
}
unordered_multiset
unordered_multiset容器与unordered_set容器的底层数据结构是一样的,都是哈希表
它们所提供的成员函数的接口都是基本一致的,这两种容器唯一的区别就是:unordered_multiset容器允许键值冗余 即unordered_multiset容器当中存储的元素是可以重复的
例子:
#include<unordered_set>
int main()
{
unordered_multiset<int> ums;
//插入元素允许重复
ums.insert(1);
ums.insert(1);
ums.insert(1);
ums.insert(2);
ums.insert(3);
for (auto& e : ums)
{
cout << e << " ";
}// 1 1 1 2 3
cout << endl;
return 0;
}
unordered_multiset容器允许键值冗余 因此成员函数find和count的意义与unordered_set容器中的含义也不同
成员函数find | 功能 |
---|---|
unordered_set容器 | 返回键值为val的元素的迭代器 |
unordered_multiset容器 | 返回底层哈希表中第一个找到的键值为val的元素的迭代器 |
成员函数count | 功能 |
---|---|
unordered_set容器 | 键值为val的元素存在则返回1,不存在则返回0(find成员函数可替代)要么为1要么为0 |
unordered_multiset容器 | 返回键值为val的元素个数(find成员函数不可替代) |
OJ练习
961.在长度2N的数组中找出重复N次的元素
https://leetcode.cn/problems/n-repeated-element-in-size-2n-array/
方法1:排序之后取中间元素
既然某个数字出现次数为数组长度一半,那么排序后,中间的俩数肯定某一个就为所求
注意:直接排序之后取中间值是会出错的!
为什么会出错呢? 原因:因为这里重复的次数只出现了一半的次数,并没有超过一半
解决方法:判断中间值和其下一个值是否相同,如果相同,则返回中间值,否则返回中间值的前一个值
class Solution {
public:
int repeatedNTimes(vector<int>& nums) {
sort(nums.begin(),nums.end());//排序
int mid = nums.size()/2;//取中间位置
//判断中间值和下一个值的关系返回答案
return nums[mid] == nums[mid+1] ?nums[mid]:nums[mid-1];
}
};
方法2:使用unordered_map 或者map 记录元素出现的次数
class Solution {
public:
int repeatedNTimes(vector<int>& nums) {
map<int,int> m; //元素 - 出现次数
//遍历原数组
for(auto& x:nums)
{
m[x]++;
}
int times = nums.size()/2;//找出现次数为nums的元素
//遍历map,找出现次数为times的元素
for(auto& kv:m)
{
//kv :pair对象,<元素,出现次数>
if(kv.second == times)
{
return kv.first;
}
}
return 0;
}
};
方法3:摩尔投票法
err版本:
注意:这里元素个数为2N个 只有一个元素重复出现N次,所以不能直接使用摩尔投票法,否则就会出错!必须元素个数超过数组长度的一般才能稳妥使用摩尔投票法
例如:[1,2,5,2,3,2]的情况, 此时ans = 3,而出现次数为N的元素是2,导致出错!本质是抵消的时候,可能出现次数为N的元素分散开,导致答案错误,所以解决办法1就是先排序
class Solution {
public:
int repeatedNTimes(vector<int>& nums) {
//摩尔投票法
int ans = 0;
int times = 0;
for(int i = 0;i<nums.size();i++)
{
if(times == 0)
{
ans = nums[i];
times = 1;
}
else if(nums[i] == ans)
{
times++;
}
else
{
times--;
}
}
return ans;
}
};
解决办法1:先排序然后进行摩尔投票,这样会保证出现次数为N次的元素连在一起,然后再和其它数抵消
class Solution {
public:
int repeatedNTimes(vector<int>& nums) {
sort(nums.begin(),nums.end());
//摩尔投票法
int ans = 0;
int times = 0;
for(int i = 0;i<nums.size();i++)
{
if(times == 0)
{
ans = nums[i];
times = 1;
}
else if(nums[i] == ans)
{
times++;
}
else
{
times--;
}
}
return ans;
}
};
方法2:先舍弃一些数
摩尔投票要求一个种类的数量必须多于一半,但是这题刚好只有一半
因此考虑怎么能让一个种类的数量变得多余一半 -> 舍弃一些数
取前三个数,如果存在重复,则直接返回 去除前三个数,对后面的数进行摩尔投票!
class Solution {
public:
int repeatedNTimes(vector<int>& nums) {
int n = nums.size();
if(nums[0] == nums[1] || nums[0] == nums[2]) return nums[0];
if(nums[1] == nums[2]) return nums[1];
int ans = 0;
int times = 0;
for(int i = 3;i<nums.size();i++)
{
if(times == 0)
{
ans = nums[i];
times = 1;
}
else if(nums[i] == ans)
{
times++;
}
else
{
times--;
}
}
return ans;
}
};
349. 两个数组的交集
https://leetcode.cn/problems/intersection-of-two-arrays/submissions/
方法1:使用两个容器s1和s2 对两个数组进行去重 ,这里可以用set
或者unordered_set
然后遍历容器s1, 如果s1的元素在s2也出现过,就是交集元素
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//使用set分别对nums1和nums2进行去重,遍历两个容器,找到二者的相同部分
set<int> s1;
set<int> s2;
//遍历nums1,进行去重
for(auto& x:nums1)
{
s1.insert(x);
}
//遍历nums2进行去重
for(auto& x:nums2)
{
s2.insert(x);
}
//遍历s1,如果s1中某个元素在s2中出现过,即为交集
vector<int> v;
for(auto& e:s1)
{
if(s2.find(e) != s2.end())
{
// s1的元素在s2出现过,这个元素就是交集元素
v.push_back(e);
}
}
return v;
}
};
方法2:用set:排序+去重
遍历两个set,取交集:
- 1.二者指向的值谁小谁就++往后走
- 2.相等就是交集的一个元素,放到容器中,然后同时++
如果要求的是差集: 做法: 1.谁小就是差集中的一个元素, 然后++ 2.相等就同时往后走
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//利用set:排序+去重
set<int> s1;
for(auto& x:nums1)
{
s1.insert(x);
}
set<int> s2;
for(auto& x:nums2)
{
s2.insert(x);
}
//遍历两个容器取交集
//如果二者指向的元素相同:就是交集的一个元素,然后二者一起走
//如果不相同,小的那个往后走
vector<int> v;
auto it1 = s1.begin();
auto it2 = s2.begin();
//当其中一个遍历到结束,就停止
while(it1 != s1.end() && it2!=s2.end())
{
if(*it1 < *it2)
{
it1++;
}
else if(*it1>*it2)
{
it2++;
}
else
{
v.push_back(*it1);
it1++;
it2++;
}
}
return v;
}
};
350. 两个数组的交集 II
https://leetcode.cn/problems/intersection-of-two-arrays-ii/
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数,对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字放入容器中,并减少哈希表中该数字出现的次数
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2)
{
//选择短的数组统计元素出现次数
map<int,int> m;//元素-出现次数
int n1 = nums1.size();
int n2 = nums2.size();
vector<int> large; vector<int> small;
if(n1>n2)
{
large = nums1;
small = nums2;
}
else
{
large = nums2;
small = nums1;
}
//统计短数组的元素出现次数
for(auto& x:small)
{
m[x]++;
}
vector<int> ans;//记录交集
//遍历长数组
for(auto& x:large)
{
//也可以用 m.count(x) 代替
if(m.find(x) != m.end())
{
ans.push_back(x);
m[x]--;//次数--
}
//如果x的出现次数为0了,就在map中删除掉,
if(m[x] == 0)
{
m.erase(x);
}
}
return ans;
}
};
优化:再次调用自己,让nums1是长数组 nums2是短数组
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return intersect(nums2, nums1);//再调用自己,然后nums1自然就成了长数组!
}
unordered_map <int, int> m;
for (auto num : nums1) {
++m[num];
}
vector<int> ans;
for (auto num : nums2) {
if (m.count(num)) {
ans.push_back(num);
--m[num];
if (m[num] == 0) {
m.erase(num);
}
}
}
return auto;
}
};
方法2:利用multiset 不去重+排序
遍历两个multiset,取交集:
- 1.二者指向的值谁小谁就++往后走
- 2.相等就是交集的一个元素,放到容器中,然后同时++
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//排序+不去重
multiset<int> s1;
multiset<int> s2;
for(auto& x:nums1)
{
s1.insert(x);
}
for(auto& x:nums2)
{
s2.insert(x);
}
vector<int> ans;
auto it1 = s1.begin();
auto it2 = s2.begin();
//当其中一个遍历到结束,就停止
while(it1 != s1.end() && it2!=s2.end())
{
if(*it1 < *it2)
{
it1++;
}
else if(*it1>*it2)
{
it2++;
}
else
{
ans.push_back(*it1);
it1++;
it2++;
}
}
return ans;
}
};
217. 存在重复元素
https://leetcode.cn/problems/contains-duplicate/
方法1:暴力双层循环 -> 过不了 超时!
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
//方法1:排序,前后元素
sort(nums.begin(),nums.end());
for(int i = 0;i<nums.size();i++)
{
//j从i+1位置开始查找
for(int j = i+1;j<nums.size();j++)
{
if(nums[i] == nums[j])
{
return true;
}
}
}
return false;
}
};
优化:一层循环,前后元素比较
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n - 1; i++) {
//i:[0,n-2] i+1:[1,n-1]
//前后元素比较
if (nums[i] == nums[i + 1]) {
return true;
}
}
return false;
}
};
方法2:使用map统计次数
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
map<int,int> m; //元素 - 出现次数
for(auto& x:nums)
{
m[x]++;
}
for(auto x:m)
{
cout << x.first<< " " << x.second<< " ";
}
//遍历map
for(auto& kv:m)
{
//kv是pair对象
//如果某一个元素出现次数>=两次就返回true
if(kv.second >= 2)
{
return true;
}
}
return false;
}
};
优化:使用unordered_set ,遍历原数组插入元素,如果发现该元素曾经出现过 -> 返回true
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> s;
for (int x: nums) {
//这个元素曾经出现过
if (s.find(x) != s.end()) {
return true;
}
s.insert(x);//插入当前元素
}
return false;
}
};
当然我们也可以这样做:因为unordered_set :去重+排序 ,所以如果全部元素插入到set之后,和原数组进行长度比较
长度不变 -> 说明没有重复的元素, 长度变短 -> 说明有重复的元素 返回true
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> s;
for (int x: nums) {
s.insert(x);//插入当前元素
}
//如果长度变短 -> 说明有重复的元素
return s.size() < nums.size();
}
};
884. 两句话中的不常见单词
https://leetcode.cn/problems/uncommon-words-from-two-sentences/
题目含义:即返回两个句子中只出现过一次的单词
因此我们可以把两个句子拼接成一个, 然后使用map映射统计句子中单词出现的次数
对于map映射中的每个键值对,键表示一个单词,值表示该单词出现的次数
在统计完成后,我们再对map映射进行一次遍历,把所有值为 1 的键放入容器中
注意点:字符串拼接要用空格间隔开!
class Solution {
public:
vector<string> uncommonFromSentences(string s1, string s2) {
//string tmp = s1+s2;//err因为会导致s1的最后一个单词和s2的第一个单词连起来!!!要用空格区分
//1.将s1和s2拼接 注意空格间隔开!!!
string tmp = s1 +" ";
tmp+=s2;
//2.利用map统计字符串出现次数
map<string,int> m;//字符串 - 出现次数
//在stringstream中会自动断开空格并停止抽出值,所以用两个while分别作两个字符串,抽出单词次数加1
stringstream str;
str << tmp;
string s;
while(str >> s) //以空格抽出一个单词
{
m[s]++;//出现次数++
}
//遍历map,找次数为1的字符串
vector<string> ans;
for(auto& kv:m)
{
//kv是pair对象
if(kv.second == 1)
{
ans.push_back(kv.first);
}
}
return ans;
}
};
如果不会使用stringstream
我们可以遍历拼接后的字符串, 把每个单词提取出来,放到容器中,然后遍历该容器用map统计次数!
class Solution {
public:
vector<string> uncommonFromSentences(string s1, string s2) {
//string tmp = s1+s2;//err因为会导致s1的最后一个单词和s2的第一个单词连起来!!!要用空格区分
//1.将s1和s2拼接 注意空格间隔开!!!
string tmp = s1 +" ";
tmp+=s2;
//2.遍历拼接后的字符串,把每个单词放到vector中
vector<string> ans;
for(int i = 0; i < tmp.size(); ++i)
{
string ret;
//以空格区分每个单词
while(i < tmp.size() && tmp[i] != ' ')
{
ret += tmp[i];
++i;
}
ans.push_back(ret);
}
//3.遍历容器ans,利用map统计字符串出现次数
map<string,int> m;//字符串 - 出现次数
for(auto& e : ans)
{
++m[e];
}
//4.遍历map,找次数为1的字符串
ans.clear();//先清空容器,存放答案
for(auto& kv:m)
{
//kv是pair对象
if(kv.second == 1)
{
ans.push_back(kv.first);
}
}
return ans;
}
};
总结:
unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构
map/set与unordered_map/unordered_set的区别
map/set 的底层是红黑树 unordered_map和unordered_set的底层是哈希表 (C++11添加的)
但是他们的功能是一样的,我们用一个表格区分:
容器名称 | 底层结构 | 内部是否有序 | CRUD效率 | 迭代器类型 |
---|---|---|---|---|
unordered_map/unordered_set | 哈希表 | 无序 | O(1) | 单向迭代器(只有正向无反向) |
map/set | 红黑树 | 有序 | O(logN) | 双向迭代器(有正向有反向) |
区别:
1.unordered_xxx 遍历不按key排序,命名就体现了 unordered:无序的
2.unordered_xxx 单向迭代器
3.unordered_xxx 综合效率高于map和set
验证: 我们可以通过下列代码测试set容器和unordered_set容器增删查的效率,
#include <iostream>
#include <set>
#include <unordered_set>
#include <time.h>
using namespace std;
int main()
{
int N = 1000;//控制数据个数为N个
vector<int> v;
v.reserve(N);
srand((unsigned int)time(NULL));//随即数种子
//随机生成N个数字
for (int i = 0; i < N; i++)
{
v.push_back(rand());//生成随机数插入
}
//测试插入效率:
//将这N个数插入set容器
set<int> s;
clock_t begin1 = clock();//记录插入set的开始时间
for (auto e : v)
{
s.insert(e);
}
clock_t end1 = clock();//记录插入set的结束时间
//将这N个数插入unordered_set容器
unordered_set<int> us;
clock_t begin2 = clock();//记录插入unordered_set的开始时间
for (auto e : v)
{
us.insert(e);
}
clock_t end2 = clock();//记录插入unordered_set的结束时间
//分别输出插入set容器和unordered_set容器所用的时间
cout << "set insert: " << end1 - begin1 << endl;
cout << "unordered_set insert: " << end2 - begin2 << endl;
//测试查找效率
//在set容器中查找这N个数
clock_t begin3 = clock();
for (auto e : v)
{
s.find(e);
}
clock_t end3 = clock();
//在unordered_set容器中查找这N个数
clock_t begin4 = clock();
for (auto e : v)
{
us.find(e);
}
clock_t end4 = clock();
//分别输出在set容器和unordered_set容器中查找这N个数所用的时间
cout << "set find: " << end3 - begin3 << endl;
cout << "unordered_set find: " << end4 - begin4 << endl;
//测试删除效率
//将这N个数从set容器中删除
clock_t begin5 = clock();
for (auto e : v)
{
s.erase(e);
}
clock_t end5 = clock();
//将这N个数从unordered_set容器中删除
clock_t begin6 = clock();
for (auto e : v)
{
us.erase(e);
}
clock_t end6 = clock();
//分别输出将这N个数从set容器和unordered_set容器中删除所用的时间
cout << "set erase: " << end5 - begin5 << endl;
cout << "unordered_set erase: " << end6 - begin6 << endl;
return 0;
}
测试结论:
- 当处理数据量小时,map/set容器与unordered_map/unordered_set容器增删查改的效率差异不大
- 当处理数据量大时,map/set容器与unordered_map/unordered_set容器增删查改的效率相比,unordered系列容器的效率更高
- 因此,当处理数据量较小时,选用xxx容器与unordered_xxx容器的差异不大;当处理数据量较大时,建议选用对应的unordered_xxx容器
当需要存储的序列为有序时,应该选用map/set容器