【C++】STL——set和map及multiset和multiset的介绍及使用


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


map和set底层结构都是二叉搜索树。

关联式容器

在前面学过的STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。本篇介绍的set和map就是关联式容器。

关联式容器中数据和数据之间有非常强的关联关系,不能随意插入。

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且英文单词与其中文含义是一一对应的关系,即通过输入单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 seco_type;
	T1 first;//key
	T2 second;//value
	pair():first(T1()),second(T2()){}//默认构造
	pair(const T1& a, const T2& b):first(a),second(b){}//构造函数
};

在上面的pair类中:

  • 是一个模板类,可以存放任意类型的键对值;
  • 有两个成员变量:first和second;

一般使用first表示key,second表示value。 键值对要在类外进行访问,所以使用struct定义类而不用class。

make_pair

该函数用来创建pair类型的匿名对象。

根据上面pair类的定义,我们要想创建一个pair类型的匿名对象需要像下面这样写:

pair<string,int>("苹果",1);

上面的方式还需要指定参数类型,所以为了方便,就封装了一个函数模板make_pair,用来快速创建一个匿名对象:

make_pair("苹果",1);

make_pair函数模板的定义如下:

image-20230725171320265

树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。

树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。 与树形结构容器不同的是,哈希结构容器中的元素是一个无序的序列。

set

认识set

  • set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列(默认是升序);

  • set虽然是采用了键值对的方式存储数据,但是在set中,value就是key,即相当于是K模型,并且每个value必须是唯一的,不可以重复,所以可以使用set进行去重;

  • 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对;

  • set中的元素不能在容器中修改(元素总是const),因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。但是可以从容器中插入或删除它们;

  • 在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较(默认是升序);

  • set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代;

  • set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。

set模板参数列表

image-20230725174304374

第一个模板参数T: set中存放元素的类型,实际在底层存储<value, value>的键值对;
Compare(仿函数):set中元素默认按照小于来比较(默认是升序);
Alloc:set中元素空间的管理方式,默认使用STL提供的空间配置器管理。

构造函数

image-20230725174650796

构造函数有三个,分别是默认构造函数、使用迭代器区间的构造函数、拷贝构造函数。

下面用代码来使用上面三个构造函数:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
	set<int> s1;//默认构造函数
	vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s2(v.begin(), v.end());//迭代器区间构造
	set<int> s3(s2);//拷贝构造
	//打印s1
	for (auto& e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	//s1内容为空
	//打印s2
	for (auto& e : s2)
	{
		cout << e << " ";
	}
	cout << endl;
	//s2打印出来为4 5 8 9 10 11 22 66 77 99
	
	//打印s3
	for (auto& e : s3)
	{
		cout << e << " ";
	}
	cout << endl;
	//s3打印出来为4 5 8 9 10 11 22 66 77 99
}

运行结果分析:

image-20230726075359482

根据结果我们可以看到set具有排序的功能(默认是升序),而且在数组中相同的元素经放在set容器之后就没有相同的元素了,说明了set具有去重的功能。set具有去重的功能,其底层是二叉搜索树,不能存放相同的元素。

迭代器

set的输出结果是升序的,其底层是二叉搜索树,打印二叉搜索树时使用中序打印出来的就是升序,所以set的迭代器在++时,移动的顺序就是中序遍历的顺序,所以使用迭代器遍历时得到的结果和二叉搜索树中序遍历得到的结果一致。

set迭代器的使用和之前学过的容器一样:

image-20230726080313203

具体使用示范代码:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
	vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//反向迭代器的使用
	set<int>::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

运行结果分析:

image-20230726080905995

插入(insert)

image-20230726081526910

1、插入一个指定值并且返回一个键值对,返回的键值对中迭代器指向新插入的位置。

返回的是pair类型的对象,其中pair的第一个成员是迭代器,其指向的是插入的元素在set中的位置;第二个成员是bool类型,如果成功插入pair类中的第二个成员为true,插入失败pair类中的第二个成员为false。所以这里insert能够做到两个功能:查找和插入(查找成功返回false,查找失败返回true)。

pair<iterator,bool> insert (const value_type& val);

2、在指定的位置插入数据,并且返回插入的位置:

iterator insert (iterator position, const value_type& val);

3、将一段迭代器区间插入到set对象中:

template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

向set中插入一个对象,并且能够保持结构不变,依然符合平衡搜索树。

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
	vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//构造一个pair类型的对象
	pair<set<int>::iterator, bool> p;
	//接收插入之后的返回值
	p = s1.insert(67);
	it = s1.begin();
	//打印插入的值和是否成功
	cout << *(p.first) << ":" << p.second << endl;

	p = s1.insert(67);
	//打印插入的值和是否成功
	cout << *(p.first) << ":" << p.second << endl;
}

打印结果:

image-20230726083811202

指定位置插入有可能会破环二叉搜索树的结构,所以不建议使用指定位置插入。

删除(erase)

image-20230726084117189

删除指定迭代器位置的值:

void erase (iterator position);

删除指定值并且返回删除指定值的个数(在set容器中没有重复值所以返回值为1,在multiset中才能体现出来):

size_type erase (const value_type& val);

删除迭代器区间中所有的值(迭代器区间是set的迭代器区间):

void erase (iterator first, iterator last);

在set中删除一个数据,并且保持结构不变。

查找(find)

iterator find (const value_type& val) const;

查找set中某个元素,如果存在返回所在位置的迭代器,如果不存在,就返回迭代器end()。

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
	vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	set<int>::iterator it1 = s1.find(66);//存在返回所在值的迭代器位置
	set<int>::iterator it2 = s1.find(666);//不存在返回end()
	if (it1 != s1.end())
		cout << "该值存在" << ":" << *(it1) << endl;
	else
		cout << "该值不存在" << endl;
}

这里和find功能类似的还有一个count函数:

size_type count (const value_type& val) const;

返回指定数据的个数,如果不存在则返回0。

但是由于set具有去重的功能,所以这里的返回值要么是1,要么是0,在set容器中可以用来查找数据是否存在。

获取上下边界

获取下边界(以传入的值为下限,返回一个迭代器):

iterator lower_bound (const value_type& val) const;

获取上边界(以传入的值为上限,返回一个迭代器):

iterator upper_bound (const value_type& val) const;

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
	vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//输出4 5 8 9 10 11 22 66 77 99
	set<int>::iterator it1 = s1.lower_bound(11);//获取11所在位置的迭代器
	set<int>::iterator it2 = s1.upper_bound(66);//获取66下一个数据的迭代器
	s1.erase(it1, it2);//删除是左闭右开的,获取66下一个位置的迭代器才能将66也删除
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//输出4 5 8 9 10 77 99
}

其他相关操作

函数函数说明
swap交换两个set对象
clear清除set对象中所有数据,但是不删除set对象
empty()判断set是否为空,如果为空返回true,否则返回flase
size()返回容器的数据个数,类型为size_t(unsigned int)
max_size()返回容器中最多能够存放的数据个数

multiset

multiset和set几乎一致,在同一个头文件中,set的功能是排序和去重,而multiset容器仅仅完成排序,容器中允许有相同的数据存在(区别在于multiset支持数据重复,而set不可以)。

在set容器中count函数和erase函数中第二个重载函数(返回被删除个数)没有实际作用,但在multiset中就能够体现它们的作用:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
    vector<int> v = { 66,5,4,11,66,66,9,22,10,4};
    multiset<int> s1(v.begin(), v.end());//迭代器区间构造
    //正向迭代器的使用
    multiset<int>::iterator it = s1.begin();
    while (it != s1.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;
    //输出4 4 5 9 10 11 22 66 66 66
    int num1 = s1.count(66);//统计元素66的数据个数
    cout << num1 << endl;//输出3
    int num2 = s1.erase(66);//删除元素66并且返回删除个数
    cout << num2<< endl;//输出3
    it = s1.begin();
    while (it != s1.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;
}

在查找函数find中返回的迭代器位置是查找到的第一个数据的迭代器位置。

map

map是一个关联式容器,底层是二叉搜索树。

image-20230726093903423

map中存放的是键值对,有两个模板参数Key和T,组成键值对。

认识map

  • map是关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素;
  • 在map中,键值key通常用于排序和唯一的标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
  • 在内部,map中的元素总是按照键值key进行比较排序的;
  • map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列);
  • map支持下标访问符,即在[]中放入key,就可以找到与key对应的value;
  • map的底层结构为平衡二叉搜索树(红黑树)。

构造函数

image-20230726095507222

默认构造函数创建的map对象为空

explicit map (const key_compare& comp = key_compare(),
              const allocator_type& alloc = allocator_type());

插入时可以使用pair创建对象,也可以使用make_pair创建匿名对象:

#include <iostream>
#include <map>
using namespace std;
int main()
{
	map<string, int> fruit;
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
	//插入时可以使用pair创建对象,也可以使用make_pair创建匿名对象
	pair<string, int> p("苹果", 1);
	fruit.insert(p);
    
	fruit.insert(make_pair("香蕉", 1));
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
	return 0;
}

打印结果:

image-20230726100234187

也可以使用迭代器区间进行构造:

template <class InputIterator>
  map (InputIterator first, InputIterator last,
       const key_compare& comp = key_compare(),
       const allocator_type& alloc = allocator_type());

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{
	vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };
	map<string, int> fruit(v.begin(),v.end());//使用vector迭代器区间进行构造
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
    //进行拷贝构造
    //auto f(fruit);//可以使用自动推导类型
    map<string, int> f(fruit);
	for (auto& e : f)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

打印结果:

image-20230726100759204

注意:

map具有去重的功能,不能插入相同的键值对(不支持重复的数据),同时不能插入key值相同value值不同的键值对。

map的判断逻辑都是只看key值,即first所表示的变量。

插入(insert)

image-20230726102534838

map的插入函数和set的一样,只是map中插入的是键值对。

第一个函数中返回的是一个键值对,返回的键值对pair<iterator,bool>,first是插入键值对所在位置的迭代器,second是bool值,如果插入成功bool值为true,插入失败bool值为false。

迭代器区间插入时,如果map中已经存在key值就不再进行插入。

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{
	map<string, int> fruit;//使用默认构造函数
	pair<map<string, int>::iterator, int> ret = fruit.insert(make_pair("黄瓜", 6));//插入一个键值对,返回键值对
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };
	fruit.insert(v.begin(), v.end());//插入迭代器区间的数据
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

打印结果:

image-20230726103605498

除了像插入这种增加操作,需要一个键值对,其他操作都是根据键值对中的键值key来处理的。

查找(find)

      iterator find (const key_type& k);
const_iterator find (const key_type& k) const;

根据指定要查找键值对中的键值key去查找,find函数根据key去查找是否存在的。

如果存在,找到以后返回键值对所在位置的迭代器,没有找到返回end迭代器。

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{
	map<string, int> fruit;
	vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };
	fruit.insert(v.begin(), v.end());
	for (auto& e : fruit)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	map<string,int>::iterator ret = fruit.find("香蕉");
	if (ret != fruit.end())
		cout << (*ret).first << ":" << (*ret).second << endl;
		//cout << ret->first << ":" <<  ret->second << endl;
	else
		cout << "找不到" << endl;
}

在上面的代码中cout << (*ret).first << ":" << (*ret).second << endl;输出的时候我们可以先对迭代器解引用再调用first和second,但是平时使用更多的还是->cout << ret->first << ":" << ret->second << endl;这种方式,这里应该有两个->但是这里省略了一个。

打印结果:

image-20230726105107481

map和set一样,不支持修改,因为可能会破坏二叉搜索树的结构

operator[]

我们可以使用map统计字符数组中水果出现的次数:

#include <iostream>
//#include <set>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
		"苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		//通过查找函数,找到返回所在位置的迭代器,没找到返回end迭代器
		map<string, int>::iterator it = countMap.find(e);
		if (it == countMap.end())
		{
			//等于end迭代器时,插入
			countMap.insert(make_pair(e, 1));
		}
		else
		{
			//不等于时second++
			it->second++;
		}
	}
	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

打印结果:

image-20230726111618617

但是在map中也可以使用[],返回的是第二个模板参数,所以上面的代码可以这样写:

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
		"苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;
	}
	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

operator[]在库中的声明:

image-20230726112056475

可以看到函数参数是键值对中的key值,也就是说key值充当下标。

image-20230726154128391

调用此函数相当于调用:

(*((this->insert(make_pair(k,mapped_type()))).first)).second

image-20230726160822860

总结来说三个步骤:

1、调用insert函数插入键值对;

2、获取insert函数插入成功之后返回的键值对中的first迭代器参数;

3、拿到该迭代器位置键值对中的second,并返回。

对应分解代码如下:

mapped_type& operator[](const key_type& k)
{
	//1、调用insert函数插入键值对
	pair<map<k,mappd_typed()>::iterator, bool> ret = insert(make_pair(k, mapped_type()));
	//2、获取insert函数插入成功之后返回的键值对中的first迭代器参数;
	map<k,mappd_typed()>::iterator it = ret.first;
	//3、拿到该迭代器位置键值对中的second,并返回
    return (*it).second;
	//return it->second;
}

也可以这样理解:

V& operator[](const K& key)
{
    //1、查找 2、插入
	pair<map<k,V()>::iterator, bool> ret = insert(make_pair(key, V());
    //3、修改
	return ret.first->second;
}

那么countMap[e]++;是如何实现插入、查找、修改的呢?

首先插入e,map中如果不存在e,则boo设置为true,成功插入,此时返回的是插入位置second的引用,引用为0,++之后变成1;如果map中已经存在e,bool值为false,直接返回e位置键值对的second的引用,并且++。

其他相关操作

函数函数说明
begin和end获取容器中第一个元素的迭代器和最后一个元素下一个位置的迭代器
rbegin和rend获取容器中最后一个元素的迭代器和第一个元素前一个位置的迭代器
size获取容器中元素的个数
empty判断容器是否为空
clear清空容器
swap交换两个容器中的数据
count获取容器中指定key值的元素个数

multimap

multimap和map都在头文件map中,multimap的使用和map一样(multimap支持相同数据,而map不支持)。

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
	vector<pair<string, int>> v = { {"西瓜",1},{"香蕉" ,2},{"西瓜",2},{"芒果",3} };
	multimap<string, int> countMap;
	for (auto& e : v)
	{
		countMap.insert(e);
	}
	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

打印结果:

image-20230726163617862

但是要注意的是multimap中没有重载[],因为multimap中允许有多个重复键值对,如果重载的话不知道返回哪个。

image-20230726163832067

multimap的inser函数中,没有返回键值对的,返回的是键值对插入之后所在位置的迭代器。

map和set在OJ中的使用

复制带随机指针的链表

题目:复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

你的代码 只 接受原链表的头节点 head 作为传入参数。

示例 1:

img

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

img

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

img

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.randomnull 或指向链表中的节点。

思路:

可以使用map容器让源节点和拷贝结点之间建立kv关系,可以根据map中的kv关系找到random指针指向。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*,Node*> copyNodeMap;
        Node* cur = head;
        Node* copyhead = nullptr;
        Node* copytail = nullptr;
        //先将链表中的结点都拷贝
        while(cur)
        {
            //创建新结点
            Node* copy = new Node(cur->val);
            //源节点和拷贝结点建立链接关系
            copyNodeMap[cur] = copy;
            if(copytail == nullptr)
            {
                copyhead = copytail = copy;
            }
            else
            {
                copytail->next = copy;
                copytail = copytail->next;
            }
            cur = cur->next;
        }
        cur = head;
        Node* copy = copyhead;
        while(cur)
        {
            if(cur->random == nullptr)
                copy->random = nullptr;
            else
            {
                //根据链接关系找到random指针指向
                copy->random = copyNodeMap[cur->random];
            }
            cur = cur->next;
            copy = copy->next;
        }
        return copyhead;
    }
};

前K个高频单词

题目:前K个高频单词

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序排序。

示例1:

输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i""love" 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 "i""love" 之前。

示例2:

输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny""day" 是出现次数最多的四个单词,出现次数依次为 4, 3, 21 次。

注意:

  • 1 <= words.length <= 500
  • 1 <= words[i] <= 10
  • words[i] 由小写英文字母组成。
  • k 的取值范围是 [1, 不同 words[i] 的数量]

思路一:

可以先使用map将words中的单词统计出现次数,因为要根据出现频率由高到低排序,所以必须要将出现次数设置为key,所以可以将second设置为key,first设置为value放入vector数组中(插入之后不能使用sort对key进行排序,sort是不稳定的排序算法,可以使用库中提供的稳定的排序算法stable_sort,),因为stable_sort函数默认是升序,所以可以实现一个仿函数用来由高到低排序;最后将排完序之后的vector中前k个键值对的second(单词)插入到要返回的vector。

要注意在实现仿函数时,参数是pair类型,注意是first参数的比较还是second参数的比较。

思路一:

可以先使用map将words中的单词统计出现次数,然后将其放入vector数组中,对vector数组进行排序,(sort是不稳定的排序算法,可以使用库中提供的稳定的排序算法stable_sort)因为stable_sort/sort函数默认是升序,所以可以实现一个仿函数用来由高到低排序(要注意在实现仿函数时,参数是pair类型,注意是first参数的比较还是second参数的比较);最后将排完序之后的vector中前k个键值对中的first参数(字符)尾插到要返回的vector中。

map不能直接使用sort,因为sort底层是一个随机迭代器,而map底层是一个双向迭代器,所以可以先将数据放到vector中再排序。

代码实现:

class Solution {
public:
    //仿函数
    struct Greater{
        bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
        {
            //出现次数大的排前面,当出现次数相同时字母小的排前面
            return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> countwords;
        //统计单词出现次数
        for(auto& e : words)
        {
            countwords[e]++;
        }
        //放入vector数组中
        vector<pair<string,int>> v1(countwords.begin(),countwords.end());
        //排序,放入仿函数
        stable_sort(v1.begin(),v1.end(),Greater());
        //sort(v1.begin(),v1.end(),Greater());
        vector<string> v2;
        //将符合条件的放入vector数组中
        for(int i = 0; i < k; i++)
        {
            v2.push_back(v1[i].first);
        }
        return v2;
    }
};

思路二:

上面的思路我们使用的是sort进行排序,虽然map本身就能排序,但是因为map的本质是排序加去重,所以不能使用,但是可以使用multimap,这里要注意当两个键值相同的时候要如何保证value的顺序,可以通过仿函数来控制。

代码实现:

class Solution {
public:
    //仿函数
    struct Greater{
        bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2) const
        {
            //出现次数大的排前面,当出现次数相同时字母小的排前面
            return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> countwords;
        //统计单词出现次数
        for(auto& e : words)
        {
            countwords[e]++;
        }
        //这里使用multiset,这里使用仿函数比较
        multiset<pair<string,int>,Greater> sortset(countwords.begin(),countwords.end());
        vector<string> v2;
        //放入vector数组中
        multiset<pair<string,int>,Greater>::iterator it = sortset.begin();
        //auto it = sortset.begin();
        while(k--)
        {
            v2.push_back(it->first);
            ++it;
        }
        return v2;
    }
};

注意这里的仿函数后面要加上const(避免权限放大问题),不然multiset<pair<string,int>,Greater> sortset(countwords.begin(),countwords.end());这里会报错。

两个数组的交集

题目:两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路:

首先就要对已有的两个数组去重,算法库里面有一个去重算法函数unique(必须是有序数组才能去重,可以先sort再去重);这里可以直接使用set;排好序之后找交集:

image-20230726193949626

代码:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //去重+排序
        // set<int> s1;
        // set<int> s2;
        // for(auto& e : nums1)
        // {
        //     s1.insert(e);
        // }
        // for(auto& e : nums2)
        // {
        //     s2.insert(e);
        // }

        //也可以直接使用迭代器区间构造
        set<int> s1(nums1.begin(),nums1.end());
        set<int> s2(nums2.begin(),nums2.end());
        //set<int>::iterator it1 = s1.begin();
        //set<int>::iterator it2 = s2.begin();
        auto it1 = s1.begin();
        auto it2 = s2.begin();
        vector<int> v;
        //在s1里面找s2里的数据
        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 == *it2)
            {
                v.push_back(*it1);
                ++it1;
                ++it2;
            }
            else if(*it1 < *it2)
            {
                it1++;
            }
            else{
                it2++;
            }
        }
        return v;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s概念-StatefulSet

StatefulSet 是用来管理有状态应用的控制器 StatefulSet 用来管理某Pod集合的部署和扩缩&#xff0c; 并为这些 Pod 提供持久存储和持久标识符StatefulSet | KubernetesStatefulSet 运行一组 Pod&#xff0c;并为每个 Pod 保留一个稳定的标识。 这可用于管理需要持久化存储或稳…

云曦暑期学习第三周——ctfshow--php特性(89-104)

目录 web89 preg_match函数 、数组 web90 intval()函数、强比较 web91 正则修饰符 web92 intval()函数、弱比较 web93 八进制、小数点 web94 strpos() 函数、小数点 web95 小数点 web96 highlight_file() 下的目录路径 web97 数组 web98 三目运算符 web9…

CMake简介

文章目录 为什么需要头文件为什么 C 需要声明头文件 - 批量插入几行代码的硬核方式头文件进阶 - 递归地使用头文件 CMake什么是编译器多文件编译与链接CMake 的命令行调用为什么需要库&#xff08;library&#xff09;CMake 中的静态库与动态库CMake 中的子模块子模块的头文件如…

企业邮箱费用详解!了解企业邮箱的费用及其相关信息

对于需要可靠的邮箱平台的企业来说&#xff0c;企业邮箱可能是最好的解决方案。有许多供应商提供企业邮箱服务&#xff0c;他们通常每月都有相应的费用。 在考虑企业邮箱的成本时&#xff0c;有几件事要记住。首先&#xff0c;您应该考虑使用邮箱服务的用户数量&#xff0c;因为…

基于Web智慧森林防火GIS监测预警可视化系统

森林火灾是森林最危险的敌人&#xff0c;也是林业最可怕的灾害&#xff0c;它会给森林带来毁灭性的后果。 建设背景 森林火灾&#xff0c;重在预防。随着现代技术的快速发展&#xff0c;数字化森林监控已成为及早发觉&#xff0c;排除森林火灾隐情的必要手段。充分利用现代科…

二、搜索与图论6:Dijkstra 模板题+算法模板(Dijkstra求最短路 I, Dijkstra求最短路 II,1003 Emergency)

文章目录 算法模板Dijkstra题目代码模板朴素dijkstra算法堆优化版dijkstra 树与图的存储(1) 邻接矩阵&#xff1a;(2) 邻接表&#xff1a;关于e[],ne[],h[]的理解 关于堆的原理与操作 模板题Dijkstra求最短路 I原题链接题目思路题解 Dijkstra求最短路 II原题链接题目思路题解 1…

搭建自己的Git服务器

环境 服务端&#xff1a;Ubuntu 22.04 客户端&#xff1a;Win11_x64 前提条件&#xff1a;需要确保在Windows机器上能够ping通Ubuntu服务器, 并且服务端与客户端均已安装了Git软件 服务端上的配置操作 以Ubuntu服务器作为Git服务端的运行环境&#xff0c;并方便后期免密推…

aws的EC2云服务器自己操作记录

亚马逊官网有免费试用1年的服务器 以下内容参考 1. 启动生成实例 1.1 创建实例时需要生成 使用的默认的 Debian 和 一个.pem后缀的秘钥 1.2 网上下一个Mobaxterm ,实例名是公有 IPv4 DNS 地址 ,使用SSH连接,登录名是admin 1.3 登录进去后 输入用户名 admin 后进去,sudo …

聊聊我的故事-悲惨的童年

目录 前言一、介绍二、17年回顾1.出生2.上幼儿园3.上小学4.上初中 高中总结 前言 本人是06年生的&#xff0c;快18了&#xff0c; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、介绍 本人已经17了&#xff0c;在这17年过的很悲惨&#xff0c;也…

QT--day4(定时器事件、鼠标事件、键盘事件、绘制事件、实现画板、QT实现TCP服务器)

QT实现tcpf服务器代码&#xff1a;&#xff08;源文件&#xff09; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化空间server new QTc…

命令模式——请求发送者与接收者解耦

1、简介 1.1、概述 在软件开发中&#xff0c;经常需要向某些对象发送请求&#xff08;调用其中的某个或某些方法&#xff09;&#xff0c;但是并不知道请求的接收者是谁&#xff0c;也不知道被请求的操作是哪个。此时&#xff0c;特别希望能够以一种松耦合的方式来设计软件&a…

使用 GitHub Copilot 进行 Prompt Engineering 的初学者指南(译)

文章目录 什么是 GitHub Copilot ?GitHub Copilot 可以自己编码吗&#xff1f;GitHub Copilot 的底层是如何工作的&#xff1f;什么是 prompt engineering?这是 prompt engineering 的另一个例子 使用 GitHub Copilot 进行 prompt engineering 的最佳实践提供高级上下文&…

0139 数据链路层1

目录 3.数据链路层 3.1数据链路层的功能 3.2组帧 3.3差错控制 3.4流量控制与可靠传输机制 3.5介质访问控制 部分习题 3.数据链路层 3.1数据链路层的功能 3.2组帧 3.3差错控制 3.4流量控制与可靠传输机制 3.5介质访问控制 部分习题 1.数据链路层协议的功能不包括&…

【雕爷学编程】MicroPython动手做(30)——物联网之Blynk 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

docker删除容器(步骤详解)

要在Docker中删除容器&#xff0c;需要使用命令docker rm。 下面是详细步骤&#xff1a; 1. 首先&#xff0c;使用docker ps命令查看当前正在运行的容器。这个命令会列出所有正在运行的容器的ID、名称、状态等信息。 如果没有正在运行的容器可以通过docker ps -a 查看当前所…

数据结构 | Radix Tree 树

什么是基数树&#xff1f; 基数树是一种多叉搜索树&#xff0c;数据位于叶子节点上&#xff0c;每一个节点有固定的2^n个子节点&#xff08;n为划分的基大小&#xff0c;当n为1时&#xff0c;为二叉树&#xff09;。 什么为划分的基&#xff1f; 以一个64位的长整型为例&#x…

Day08-作业(MySQLMybatis入门)

作业1&#xff1a;多表查询 数据准备&#xff1a; 重新创建一个数据库 db03_homework 执行如下脚本&#xff0c;创建表结构&#xff0c;导入测试数据 -- 部门管理 create table tb_dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not n…

ubuntu git操作记录设置ssh key

用到的命令&#xff1a; 安装git sudo apt-get install git配置git用户和邮箱 git config --global user.name “用户名” git config --global user.email “邮箱地址”安装ssh sudo apt-get install ssh然后查看安装状态&#xff1a; ps -e | grep sshd4. 查看有无ssh k…

通过案例实战详解elasticsearch自定义打分function_score的使用

前言 elasticsearch给我们提供了很强大的搜索功能&#xff0c;但是有时候仅仅只用相关度打分是不够的&#xff0c;所以elasticsearch给我们提供了自定义打分函数function_score&#xff0c;本文结合简单案例详解function_score的使用方法&#xff0c;关于function-score-query…

【Spring】深究SpringBoot自动装配原理

文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…