【C++】unordered_map unordered_set 练习题

news2024/11/22 23:34:59

文章目录

  • 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

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value
  2. 在unordered_map中,键值通常用于唯一地标识元素,不允许重复键值,而映射值是一个对象,其内容与此键关联键和映射值的类型可能不同
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低
  5. unordered_maps实现了直接访问操作符**(operator[]),**它允许使用key作为参数直接访问value
  6. 它的迭代器至少是前向迭代器

需要引用头文件:#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

  1. unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素
  2. 在unordered_set中,元素的值同时也是唯一地标识它的key ,unordered_set有去重操作!!插入相同的key不起作用 unordered_set可以认为是<key,key>类型的键值对
  3. 在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key, unordered_set将相同哈希值的键值放在相同的桶中
  4. unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低
  5. 它的迭代器至少是前向迭代器

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/

image-20220524172150849

方法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/

image-20220524175000047

方法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/

image-20221009215338030

由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数,对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值

首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字放入容器中,并减少哈希表中该数字出现的次数

为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集

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/

image-20220525224928496

方法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/

题目含义:即返回两个句子中只出现过一次的单词

image-20220525230621255

因此我们可以把两个句子拼接成一个, 然后使用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容器


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

( 回溯算法) 27. 移除元素 ——【Leetcode每日一题】

❓27. 移除元素 难度&#xff1a;简单 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以…

ip地址段分解与合并

1、为什么要分解和合并ip地址段 无他&#xff0c;工作需要嘛&#xff0c;谁没事去划分ip地址段 优点&#xff1a;可以节省大量的时间&#xff0c;减少算错的可能性 2、工具下载 下载链接&#xff1a; https://github.com/zhanhb/cidr-merger github在国内使用不太友好&#…

14、IIC主机控制--引脚软件模拟

时序图&#xff1a; 软件基于STM32 HAL库 IIC–定时器精确延时 软件用涉及到使用定时器做精确延时&#xff0c;可以参考我的文章–“CubeMx 定时器高精度延时” 延时使用的文件&#xff1a; tim.c /*********************************************************************…

Linux基础内容(21)—— 进程消息队列和信号量

Linux基础内容&#xff08;20&#xff09;—— 共享内存_哈里沃克的博客-CSDN博客 目录 1.消息队列 1.定义 2.操作 2.信号量 1.定义 2.细节 3.延申 4.操作 3.IPC的特点共性 1.消息队列 1.定义 定义&#xff1a;是操作系统提供的内核级队列 2.操作 msgget&#xff1a;…

Java实现MQTT传输协议通信

Java实现MQTT传输协议通信 1. MQTT1.1 概述1.2 发布和订阅模型1.3 客户端1.4 服务器1.5 订阅、主题、会话1.6 协议中的方法2. Java使用MQTT2.1 添加 pom 依赖2.3 订阅方2.4 发布方2.4 MQTT 连接创建方式2.4.1 普通 TCP 连接2.4.2 TLS/SSL 连接1. MQTT

java_day01_单元测试_配置文件

一、软件的生命周期 **软件的可行性分析:**分析该软件是否值的研发,会消耗多少成本,能带来多少的利益等分析 **需求分析:**分析该软件具体该具备有那些功能,产品经理与客户一起讨论 **软件设计:**该软件应该使用什么样的架构,用什么样的数据库,每个模块的具体功能 **程序编…

2023年8大黑客编程语言

以下是2023年最适合黑客攻击的8种编程语言的列表。 道德黑客被定义为合法进入各种网络的做法&#xff0c;目的是识别黑客可能利用的潜在弱点来访问网络。此类黑客攻击旨在在任何漏洞进入危险攻击者手中之前发现它们&#xff0c;然后及时修复它们以防止攻击。让我们进入文章&am…

【数字通信】PAM基带信号的功率谱原理推导详解

PAM信号可以说是最简单的数字通信信号,很多理论最初都是由该信号的表达式推导得到并进行拓展的,纵观各类数字信号的表达式,或多或少都有PAM信号的“影子”,也就是说PAM信号相关的理论知识是最基本的,很有必要搞清楚,本博客主要讨论PAM基带信号的功率谱的原理及推导过程,…

我干了8年测试,告诉你现在软件测试还能不能找到工作!

观点&#xff1a;如果你还是以前的思维来学习测试&#xff0c;那你肯定是找不到工作&#xff01; 我做测试工作有将近8年的时间&#xff0c;蚂蚁金服做过2年&#xff0c;因为加班太多离职了。目前在一家国企上市公司&#xff0c;一年能拿三四十个左右&#xff0c;对比头部互联…

系统集成项目管理工程师 下午 真题 及考点(2018年下半年)

文章目录 一&#xff1a;第4章 项目管理一般知识&#xff0c;项目管理办公室的职责。第6章 项目整体管理二&#xff1a;第5章 项目立项管理。第14章 项目采购管理&#xff0c;采购文件。第13章 项目合同管理&#xff0c;按项目 付款方式 划分的合同分类三&#xff1a;第9章 项目…

GB/T28181-2022针对H.265编码细化及技术实现

技术背景 新版国家标准GB/T28181-2022《公共安全视频监控联网系统信息传输、交换、控制技术要求》已于2022年12月30日发布&#xff0c;并将于2023年7月1日正式实施。 国家标准GB/T28181-2022《公共安全视频监控联网系统信息传输、交换、控制技术要求》规定了公共安全视频监控…

RocketMQ学习

各MQ 并发性能比较 吞吐量 kafka 17.3w/s rocketMQ 11.6w/s RabbitMQ 5.96w/s RocketMQ组件 broker 核心业务组件 nameServe 保存broker 的ip、端口、上下线信息等。 类似注册中心 启动nameServe 时会调用 runserver 启动broker &#xff0c;会默认读取/conf/broker.conf …

第3章“程序的机器级表示”:程序编码 和 数据格式

文章目录 3.2 程序编码3.2.1 机器级代码3.2.2 代码示例3.2.3 关于格式的注解 3.3 数据格式 3.2 程序编码 假设写一个 C 程序&#xff0c;有两个文件 p1.c 和 p2.c。然后用 Unix 命令行编译这段代码&#xff1a; unix> gcc -O2 -o p p1.c p2.c命令 gcc 表明的就是 GNU C 编…

嵌入式 Linux 入门(十一、make 和 MakeFile)

嵌入式 Linux 入门第十一课&#xff0c;Make 工具和 Makefile 的引入...... 矜辰所致目录 前言一、Linux 下多文件编译二、make 工具和 Makefile2.1 make 和 Makefile 是什么?2.2 通过 STM32 提前熟悉 Makefile2.3 GCC 与 make 的关系/区别&#xff1f; 三、一个简单的 Makefi…

〖Python网络爬虫实战㉙〗- Selenium案例实战(三)

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

OpenGL之着色器

文章目录 什么是着色器数据类型输入与输出Uniform三角形渐变色例子从文件中读取 什么是着色器 着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的&#xff0c;它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本&#xff0c;接着是输入和输…

攻击面管理有多重要?从一个社工钓鱼的仿冒网站说起

2023年4月中旬&#xff0c;A企业紧锣密鼓地展开了重保前期的筹备。A企业是一家集团公司&#xff0c;业务范围广&#xff0c;资产众多&#xff0c;为了提前了解自身安全情况&#xff0c;探知未知风险&#xff0c;公司通过自身资产清单及配套手段对自身资产暴露情况进行了梳理。 …

总结springboot项目中一些后端接收前端传参的方法

文章目录 1、java方法入参里面什么注解都没有2、不使用&#xff1f;&来拼接参数&#xff0c;在参数中添加PathVariable注解3、RequestBody 先创建一个springboot项目&#xff0c;并在pom文件中添加web依赖&#xff1a; <dependency><groupId>org.springframewo…

Linux:LVM动态磁盘管理

Linux中的LVM是什么 LVM&#xff08;Logical Volume Manager&#xff09;是Linux系统中的一种动态分区技术&#xff0c;它允许将多个物理硬盘上的存储空间组合成一个或多个逻辑卷&#xff08;Logical Volume&#xff09;&#xff0c;并且可以在运行时对逻辑卷进行调整。LVM的设…

Unity UI -- (7) 创建世界空间UI

目前为止&#xff0c;我们已经设计了一个屏幕空间UI&#xff08;Screen Space UI&#xff09;。一个屏幕空间UI会在屏幕上平坦放置&#xff0c;它会被渲染到环境中所有东西的上面&#xff0c;无论相机位置在哪里。 而一个世界空间UI&#xff08;World Space UI&#xff09;能够…