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

news2024/11/24 8:31:08

前言:

mapset 是 C++ STL(标准模板库)中的两种非常重要的容器,它们基于一种叫做平衡二叉搜索树(通常是红黑树)的数据结构来实现。在 C++ 中,map 是一个键值对容器,set 只存储唯一的键,而这两个容器都通过二叉树的结构来保持数据的有序性和高效的查找、插入、删除操作。

1. 序列式容器和关联式容器

前⾯我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这 些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧 密的关联关系,⽐如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位 置来顺序保存和访问的。

关联式容器也是⽤来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来 保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

本章节讲解的map和set底层是红⿊树,红⿊树是⼀颗平衡⼆叉搜索树。set是key搜索场景的结构, map是key/value搜索场景的结构。

2. set系列的使用

2.1 set和multiset参考⽂档

set - C++ Reference (cplusplus.com)

2.2 set类的介绍

set的声明如下,T就是set底层关键字的类型

set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模 版参数

set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参 数。

⼀般情况下,我们都不需要传后两个模版参数。

set底层是⽤红⿊树实现,增删查效率是 ,迭代器遍历是⾛的搜索树的中序,所以是有序 的。 O(logN)

前⾯部分我们已经学习了vector/list等容器的使⽤,STL容器接⼝设计,⾼度相似,所以这⾥我们 就不再⼀个接⼝⼀个接⼝的介绍,⽽是直接带着⼤家看⽂档,挑⽐较重要的接⼝进⾏介绍。

2.3 set的构造和迭代器

set的构造我们关注以下⼏个接⼝即可。

set的⽀持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中 序;⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都不⽀持迭代器修改数据,修改 关键字数据,破坏了底层搜索树的结构。

// empty (1) ⽆参默认构造
explicit set(const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());


// range (2) 迭代器区间构造
template <class InputIterator>
set(InputIterator first, InputIterator last,
    const key_compare& comp = key_compare(),
    const allocator_type & = allocator_type());


// copy (3) 拷⻉构造
set(const set& x);


// initializer list (5) initializer 列表构造
set(initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());


// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type


// 正向迭代器
iterator begin();
iterator end();


// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

 

2.4 set的增删查

set的增删查关注以下⼏个接⼝即可:

Member types
key_type->The first template parameter(T)
value_type->The first template parameter(T)

// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator, bool> insert(const value_type& val);


// 列表插⼊,已经在容器中存在的值不会插⼊
void insert(initializer_list<value_type> il);


// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert(InputIterator first, InputIterator last);


// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find(const value_type& val);


// 查找val,返回Val的个数
size_type count(const value_type& val) const;


// 删除⼀个迭代器位置的值
iterator erase(const_iterator position);


// 删除val,val不存在返回0,存在返回1
size_type erase(const value_type& val);


// 删除⼀段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);


// 返回⼤于等val位置的迭代器
iterator lower_bound(const value_type& val) const;


// 返回⼤于val位置的迭代器
iterator upper_bound(const value_type& val) const;

2.5 insert和迭代器遍历使用样例

int main()
{
	//去重+升序
	/*set<int> s;*/
	set<int, greater<int>> s;
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);
	s.insert(7);
	s.insert(3);

	//set<int>::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{
		// error C3892: “it”: 不能给常量赋值
		cout << *it << " ";
		++it;
	}
	cout << endl;

	s.insert({ 2,8,3,9,2 });
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	// void insert (initializer_list<value_type> il);
	//set<string> strset = { "sort","insert","add" };
	set<string> strset({ "sort","insert","add" });
	
	// 遍历string比较ascll码大小顺序遍历的
	for (auto& e : strset)
	{
		cout << e << " ";
	}
	cout << endl;
	
	return 0;
}

运行结果:

2.6 find和erase使用样例: 

int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//删除最小值
	s.erase(s.begin());
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//直接删除x
	int x;
	/*cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在! " << endl;
	}
	else
	{
		cout << x << "删除成功!" << endl;
	}*/
	cin >> x;
	auto pos = s.find(x);
	if (pos != s.end())
	{
		//pos失效
		s.erase(pos);
		//cout<<*pos<<endl;
	}
	else
	{
		cout << x << "不存在!" << endl;
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//算法库的查找O(N)
	auto pos1 = find(s.begin(), s.end(),x);
	
	// set自身实现的查找 O(logN)
	auto pos2 = s.find(x);

	// 利用count间接实现快速查找
	cin >> x;
	if (s.count(x))
	{
		cout << x << "在!" << endl;
	}
	else
	{
	cout << x << "不存在!" << endl;
	}

	return 0;
}

2.7 返回⼤于等val和等于val的使用样例

int main()
{
	std::set<int> myset;
	for (int i = 1; i < 10; i++)
		myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;

	// [30, 50]值
	// [25, 55]值

	返回 >= 30
	//auto itlow = myset.lower_bound(30);
	返回 > 50
	//auto itup = myset.upper_bound(50);

	//返回 >= 25
	auto itlow = myset.lower_bound(25);
	//返回 > 55
	auto itup = myset.upper_bound(55);
	
	// 删除这段区间的值
	myset.erase(itlow, itup);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

3.multiset和set的差异

multiset和set的使⽤基本完全类似,主要区别点在于multiset⽀持值冗余,那么 insert/find/count/erase都围绕着⽀持值冗余有所差异,具体参看下⾯的样例代码理解。

int main()
{
	// 相比set不同的是,multiset是排序,但是不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 相比set不同的是,x可能会存在多个,find查找中序的第一个
	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;
	// 相比set不同的是,count会返回x的实际个数
	cout << s.count(x) << endl;
	去重
	//pos = s.find(x);
	//while (pos != s.end() && *pos == x)
	//{
	//	pos = s.erase(pos);
	//}
	//cout << endl;
	
	// 相⽐set不同的是,erase给值时会删除所有的x
	s.erase(x);//
	it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

运行结果:

3.1 两个数组的交集

我们来写个题试试

两个数组的交集

题目描述:

我们可以把数据放到set里面去因为set相同的值会插入失败,这样不就去重了,然后进行比较过程中让小的++

代码如下:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> set1 (nums1.begin(),nums1.end());
        set<int> set2 (nums2.begin(),nums2.end());
        vector<int> ret;
        auto it1 = set1.begin();
        auto it2 = set2.begin();
        while(it1 != set1.end() && it2 != set2.end())
        {
            if(*it1 > *it2)
            {
                ++it2;
            }
            else if(*it2 > *it1)
            {
                ++it1;
            }
            else
            {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
            
        }
        return ret;
    }
};

3.2环形列表 ||

环形列表 ||

数据结构初阶阶段,我们通过证明⼀个指针从头开始⾛⼀个指针从相遇点开始⾛,会在⼊⼝点相遇, 理解证明都会很⿇烦。这⾥我们使⽤set查找记录解决⾮常简单⽅便,这⾥体现了set在解决⼀些问题时 的价值,完全是降维打击。

思路:这里我们先遍历一遍,遍历过程中把数据插入到set的容器变量中然后用count来统计个数如果个数为0则就插入当第二次入环的时候数据已经插入进去了此时的节点就是入环节点直接返回

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        set<ListNode*>s;
        ListNode*cur = head;
        while(cur)
        {
            if(s.count(cur))
            {
                return cur;
            }
            else
            {
                s.insert(cur);
            }
             cur = cur->next;
        }
        return nullptr;
        
    }
};

4. map系列的使用

4.1map和multimap参考⽂档

map - C++ Reference

4.2 map类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持 ⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的 内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实 现,增删查改效率是 O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。

4.3pair类型介绍

typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair() : first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b) : first(a), second(b)
	{}
	template<class U, class V>
	pair(const pair<U, V>& pr) : first(pr.first), second(pr.second)
	{}
};
template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{
	return (pair<T1, T2>(x, y));
}

4.4map的构造

map的构造我们关注以下⼏个接⼝即可。

map的⽀持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛ 的中序;⽀持迭代器就意味着⽀持范围for,map⽀持修改value数据,不⽀持修改key数据,修改关键 字数据,破坏了底层搜索树的结构。

// empty (1) ⽆参默认构造
explicit map(const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());


// range (2) 迭代器区间构造
template <class InputIterator>
map(InputIterator first, InputIterator last,
    const key_compare& comp = key_compare(),
    const allocator_type & = allocator_type());


// copy (3) 拷⻉构造
map(const map& x);


// initializer list (5) initializer 列表构造
map(initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());


// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type


// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器


reverse_iterator rbegin();
reverse_iterator rend();

4.5 map的增删查

map的增删查关注以下⼏个接⼝即可:

map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全 类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value

Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>


// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator, bool> insert(const value_type& val);


// 列表插⼊,已经在容器中存在的值不会插⼊
void insert(initializer_list<value_type> il);


// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert(InputIterator first, InputIterator last);


// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find(const key_type& k);


// 查找k,返回k的个数
size_type count(const key_type& k) const;


// 删除⼀个迭代器位置的值
iterator erase(const_iterator position);


// 删除k,k存在返回0,存在返回1
size_type erase(const key_type& k);


// 删除⼀段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);


// 返回⼤于等k位置的迭代器
iterator lower_bound(const key_type& k);


// 返回⼤于k位置的迭代器
const_iterator lower_bound(const key_type& k) const;

4.6 map的数据修改

前⾯我提到map⽀持修改mapped_type 数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜 索树的结构。

map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map 还有⼀个⾮常重要的修改接⼝operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数 据,所以他是⼀个多功能复合接⼝

需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的 T映射值叫做value。

Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的
mapped_type值
iterator find(const key_type& k);
// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first
set to an iterator pointing to either the newly inserted element or to the
element with an equivalent key in the map.The pair::second element in the pair
is set to true if a new element was inserted or false if an equivalent key
already existed.


// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
⼀个是insert返回值pair<iterator, bool>
pair<iterator, bool> insert(const value_type & val);
mapped_type& operator[] (const key_type& k);

// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
    // 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
    mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊ + 修改功能
        // 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的
        迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找 + 修改的功能
        pair<iterator, bool> ret = insert({ k, mapped_type() });
    iterator it = ret.first;
    return it->second;
}

4.7 构造遍历及增删查使用样例

#include<iostream>
#include<map>
using namespace std;
int main()
{
	// initializer_list构造及迭代遍历
	map<string, string> dict = { {"left", "左边"}, {"right", "右边"},
	{"insert", "插入"},{ "string", "字符串" } };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		//cout << (*it).first <<":"<<(*it).second << endl;
		// map的迭代基本都使⽤operator->,这⾥省略了⼀个->
		// 第⼀个->是迭代器运算符重载,返回pair*,第⼆个箭头是结构指针解引⽤取pair数据
		
		//cout << it.operator->()->first << ":" << it.operator->()-> second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;// insert插⼊pair对象的4种⽅式,对⽐之下,最后⼀种最⽅便
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);
	dict.insert(pair<string, string>("second", "第二个"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert({ "auto", "自动的" });

	// "left"已经存在,插⼊失败
	dict.insert({ "left", "左边,剩余" });
	// 范围for遍历
	for (const auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	string str;
	while (cin >> str)
	{
		auto ret = dict.find(str);
		if (ret != dict.end())
		{
			cout << "->" << ret->second << endl;
		}
		else
		{
			cout << "无此单词,请重新输入" << endl;
		}
	}
	// erase等接⼝跟set完全类似,这⾥就不演⽰讲解了
	return 0;
}

运行结果:

4.8 map的迭代器和[]功能样例:

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤find和iterator修改功能,统计⽔果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// 先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}
		// 2、在,则查找到的节点中⽔果对应的次数++
		auto ret = countMap.find(str);
		if (ret == countMap.end())
		{
			countMap.insert({ str, 1 });
		}
		else
		{
			ret->second++;
		}
	}
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;

运行结果:

还可以这样 

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤[]插⼊+修改功能,巧妙实现统计⽔果出现的次数
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
	"苹果", "⾹蕉", "苹果", "⾹蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// []先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引⽤,++⼀下就变成1次了
		// 2、在,则返回⽔果对应的次数++
			countMap[str]++;
	}
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;
}

运行结果: 

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	// key不存在->插⼊ {"insert", string()}
	dict["insert"];
	// 插⼊+修改
	dict["left"] = "左边";
	// 修改
	dict["left"] = "左边、剩余";
	// key存在->查找
	cout << dict["left"] << endl;
	return 0;
}

 运行结果:

5.multimap和map的差异

multimap和map的使⽤基本完全类似,主要区别点在于multimap⽀持关键值key冗余,那么 insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这⾥跟set和multiset完全⼀样,⽐如 find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[],因为⽀持key冗余,[]就只能⽀ 持插⼊了,不能⽀持修改。

5.1 随机链表的复制

随机链表的复制

题目描述:

数据结构初阶阶段,为了控制随机指针,我们将拷⻉结点链接在原节点的后⾯解决,后⾯拷⻉节点还 得解下来链接,⾮常⿇烦。这⾥我们直接让{原结点,拷⻉结点}建⽴映射关系放到map中,控制随机指 针会⾮常简单⽅便,这⾥体现了map在解决⼀些问题时的价值,完全是降维打击。

代码如下:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*,Node*>nodeMap;
        Node*copyhead=nullptr,*copytaill=nullptr;
        Node*cur = head;
        while(cur)
        {
            if(copytaill == nullptr)
            {
                copyhead = copytaill = new Node(cur->val);
                
            }
            else
            {
                copytaill->next = new Node (cur->val);
                copytaill = copytaill->next;
            }
            // 原节点和拷⻉节点map kv存储
            nodeMap[cur] = copytaill;
            cur = cur->next;
        }
        // 处理random
        cur = head;
        Node* copy = copyhead;
        while(cur)
        {
            if(nodeMap[cur->random] == nullptr)
            {
                copy->random = nullptr;
            }
            else
            {
                copy->random = nodeMap[cur->random];
            }
            cur = cur->next;
            copy = copy->next;
        }
        return copyhead;
    }
};

5.2 692.前K个高频单词

692.前K个高频单词

题目描述:

本题⽬我们利⽤map统计出次数以后,返回的答案应该按单词出现频率由⾼到低排序,有⼀个特殊要 求,如果不同的单词有相同出现频率,按字典顺序排序。

解决思路1:⽤排序找前k个单词,因为map中已经对key单词排序过,也就意味着遍历map时,次数相同的单词, 字典序⼩的在前⾯,字典序⼤的在后⾯。那么我们将数据放到vector中⽤⼀个稳定的排序就可以实现 上⾯特殊要求,但是sort底层是快排,是不稳定的,所以我们要⽤stable_sort,他是稳定的。

代码如下:

class Solution {
public:
	struct Compare
	{
		bool operator()(const pair<string, int>& x, const pair<string, int>& y)
			const
		{
			return x.second > y.second;
		}
	};
	vector<string> topKFrequent(vector<string>& words, int k) {
		map<string, int> countMap;
		for (auto& e : words)
		{
			countMap[e]++;
		}
		vector<pair<string, int>> v(countMap.begin(), countMap.end());
		// 仿函数控制降序
		stable_sort(v.begin(), v.end(), Compare());
		//sort(v.begin(), v.end(), Compare());
		// 取前k个
		vector<string> strV;
		for (int i = 0; i < k; ++i)
		{
			strV.push_back(v[i].first);
		}
		return strV;
	}
};

解决思路2:

将map统计出的次数的数据放到vector中排序,或者放到priority_queue中来选出前k个。利⽤仿函数 强⾏控制次数相等的,字典序⼩的在前⾯。

class Solution {
public:
	struct Compare
	{
		bool operator()(const pair<string, int>& x, const pair<string, int>& y)
			const
		{
			return x.second > y.second || (x.second == y.second && x.first <
				y.first);;
		}
	};
	vector<string> topKFrequent(vector<string>& words, int k) {
		map<string, int> countMap;
		for (auto& e : words)
		{
			countMap[e]++;
		}
		vector<pair<string, int>> v(countMap.begin(), countMap.end());
		// 仿函数控制降序,仿函数控制次数相等,字典序⼩的在前⾯
		sort(v.begin(), v.end(), Compare());
		// 取前k个
		vector<string> strV;
		for (int i = 0; i < k; ++i)
		{
			strV.push_back(v[i].first);
		}
		return strV;
	}
};

用优先级队列

class Solution {
public:
	struct Compare
	{
		bool operator()(const pair<string, int>& x, const pair<string, int>& y)
			const
		{
			// 要注意优先级队列底层是反的,⼤堆要实现⼩于⽐较,所以这⾥次数相等,想要字典
			序⼩的在前⾯要⽐较字典序⼤的为真
				return x.second < y.second || (x.second == y.second && x.first >
					y.first);
		}
	};
	vector<string> topKFrequent(vector<string>& words, int k) {
		map<string, int> countMap;
		for (auto& e : words)
		{
			countMap[e]++;
		}
		// 将map中的<单词,次数>放到priority_queue中,仿函数控制⼤堆,次数相同按照字典
		序规则排序
			priority_queue<pair<string, int>, vector<pair<string, int>>, Compare>
			p(countMap.begin(), countMap.end());
		vector<string> strV;
		for (int i = 0; i < k; ++i)
		{
			strV.push_back(p.top().first);
			p.pop();
		}
		return strV;
	}
};

结束语:

总的来说,mapset 是 C++ STL 中非常强大且高效的容器,它们通过基于红黑树的实现保证了数据的有序性和操作的高效性。在处理需要频繁插入、查找和删除的任务时,mapset 提供了理想的解决方案。

无论是存储键值对的 map,还是存储唯一元素的 set,它们的时间复杂度始终保持在 O(log n),适用于很多实际应用场景,如字典、集合运算、频率计数等。

最后感谢大家的支持

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

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

相关文章

Python的函数(补充浅拷贝和深拷贝)

一、定义 函数的定义&#xff1a;实现【特定功能】的代码块。 形参&#xff1a;函数定义时的参数&#xff0c;没有实际意义 实参&#xff1a;函数调用/使用时的参数&#xff0c;有实际意义 函数的作用&#xff1a; 简化代码提高代码重用性便于维护和修改提高代码的可扩展性…

el-input 正则表达式校验输入框不能输入汉字

<el-form :model"data1" :rules"rules" ref"ruleForm" label-width"210px" class"demo-ruleForm"><el-form-item label"锯路&#xff1a;" prop"sawKref"><el-input class"inptWid…

嵌入式linux系统中I2C控制实现AP3216C传感器方法

大家好,今天主要给大家分享一下,如何使用linux系统里面的I2C进行控制实现。 第一:Linux系统中I2C简介 Linux 内核开发者为了让驱动开发工程师在内核中方便的添加自己的 I2C 设备驱动程序,更容易的在 linux 下驱动自己的 I2C 接口硬件,进而引入了 I2C 总线框架。与 Linux 下…

OceanBase 应用实践:如何处理数据空洞,降低存储空间

问题描述 某保险行业客户的核心系统&#xff0c;从Oracle 迁移到OceanBase之后&#xff0c;发现数据存储空间出现膨胀问题&#xff0c;数据空间 datasize9857715.48M&#xff0c;实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断&#xff0c;数据空洞较为严重…

【flask开启进程,前端内容图片化并转pdf-会议签到补充】

flask开启进程,前端内容图片化并转pdf-会议签到补充 flask及flask-socketio开启threading页面内容转图片转pdf流程前端主js代码内容转图片-browser端browser端的同步编程flask的主要功能route,def 总结 用到了pdf,来回数据转发和合成,担心flask卡顿,响应差,于是刚好看到threadi…

QT栅格布局的妙用

当groupBox中只有一个控件时&#xff0c;我们想要它满格显示可以对groupBox使用栅格布局

MyBatis快速入门(上)

MyBatis快速入门&#xff08;上&#xff09; 一、MyBatis 简介1、概述2、JDBC、Hibernate、MyBatis 对比 二、MyBatis 框架搭建1、开发环境2、创建maven工程3、创建MyBatis的核心配置文件4、创建mapper接口5、创建MyBatis的映射文件6、通过junit测试功能7、加入log4j2日志功能 …

在Pybullet中加载Cinema4D创建的物体

首先明确我们的目标&#xff0c;是希望在cinema4D中创建自己想要的模型&#xff0c;并生成.obj文件&#xff0c;然后在pybullet中加载.obj文件作为静态物体&#xff0c;可以用于抓取物体&#xff0c;避障物体。&#xff08;本文提到的方法只能实现静态物体的建模&#xff0c;如…

第十三届交通运输研究(上海)论坛┆智能网联汽车技术现状与研究实践

0.简介 交通运输研究&#xff08;上海&#xff09;论坛&#xff08;简称为TRF&#xff09;是按照国际会议的组织原则&#xff0c;为综合交通运输领域学者们构建的良好合作交流平台。交通运输研究&#xff08;上海&#xff09;论坛已经成功举办了十二届&#xff0c;凝聚了全国百…

Pr:视频过渡快速参考(合集 · 2025版)

Adobe Premiere Pro 自带七组约四十多个视频过渡 Video Transitions效果&#xff0c;包含不同风格和用途&#xff0c;可在两个剪辑之间创造平滑、自然的转场&#xff0c;用来丰富时间、地点或情绪的变化。恰当地应用过渡可让观众更好地理解故事或人物。 提示&#xff1a; 点击下…

stm32 踩坑笔记

串口问题&#xff1a; 问题&#xff1a;会改变接收缓冲的下一个字节 串口的初始化如下&#xff0c;位长度选择了9位。因为要奇偶校验&#xff0c;要选择9位。但是接收有用数据只用到1个字节。 问题原因&#xff1a; 所以串口接收时会把下一个数据更改

昇思大模型平台打卡体验活动:项目4基于MindSpore实现Roberta模型Prompt Tuning

基于MindNLP的Roberta模型Prompt Tuning 本文档介绍了如何基于MindNLP进行Roberta模型的Prompt Tuning&#xff0c;主要用于GLUE基准数据集的微调。本文提供了完整的代码示例以及详细的步骤说明&#xff0c;便于理解和复现实验。 环境配置 在运行此代码前&#xff0c;请确保…

后悔没早点知道,Coze 插件 + Cursor 原来可以这样赚钱

最近智能体定制化赛道异常火爆。 打开闲鱼搜索"Coze 定制",密密麻麻的服务报价直接刷屏,即使表明看起来几十块的商家,一细聊,都是几百到上千不等的报价。 有趣的是,这些智能体定制化服务背后,最核心的不只是工作流设计,还有一个被很多人忽视的重要角色 —— …

基于STM32的节能型路灯控制系统设计

引言 本项目基于STM32微控制器设计了一个智能节能型路灯控制系统&#xff0c;通过集成多个传感器模块和控制设备&#xff0c;实现对路灯的自动调节。该系统能够根据周围环境光照强度、车辆和行人活动等情况&#xff0c;自动控制路灯的开关及亮度调节&#xff0c;从而有效减少能…

Qml 模型-视图-代理(贰)之 动态视图学习

目录 动态视图 动态视图用法 ⽅向&#xff08;Orientation&#xff09; 键盘导航和⾼亮 页眉与页脚 网格视图 动态视图 动态视图用法 Repeater 元素适合有限的静态数据&#xff0c; QtQuick 提供了 ListView 和 GridView, 这两个都是基于 Flickable(可滑动) 区域的元素…

新标准大学英语综合教程1课后习题答案PDF第三版

《新标准大学英语&#xff08;第三版&#xff09;综合教程1 》是“新标准大学英语&#xff08;第三版&#xff09;”系列教材之一。本书共包含6个单元&#xff0c;从难度和话题上贴近大一上学生的认知和语言水平&#xff0c;包括与学生个人生活领域和社会文化等相关内容&#x…

Python闭包|你应该知道的常见用例(下)

引言 在 Python 编程语言中&#xff0c;闭包通常指的是一个嵌套函数&#xff0c;即在一个函数内部定义的另一个函数。这个嵌套的函数能够访问并保留其外部函数作用域中的变量。这种结构就构成了一个闭包。 闭包在函数式编程语言中非常普遍。在 Python 中&#xff0c;闭包特别有…

Rocky、Almalinux、CentOS、Ubuntu和Debian系统初始化脚本v9版

Rocky、Almalinux、CentOS、Ubuntu和Debian系统初始化脚本 Shell脚本源码地址&#xff1a; Gitee&#xff1a;https://gitee.com/raymond9/shell Github&#xff1a;https://github.com/raymond999999/shell脚本可以去上面的Gitee或Github代码仓库拉取。 支持的功能和系统&am…

AUTOSAR OS模块详解(一) 概述

AUTOSAR OS模块详解(一) 概述 本文主要介绍AUTOSAR架构下的OS概述。 文章目录 AUTOSAR OS模块详解(一) 概述1 前言1.1 操作系统1.2 嵌入式操作系统1.3 AUTOSAR操作系统 2 AUTOSAR OS2.1 AUTOSAR OS组成2.2 AUTOSAR OS类别2.3 任务管理2.4 调度表2.5 资源管理2.6 多核特性2.7 …

5位机械工程师如何共享一台工作站的算力?

在现代化的工程领域中&#xff0c;算力已成为推动创新与技术进步的关键因素之一。对于机械工程师而言&#xff0c;强大的计算资源意味着能够更快地进行复杂设计、模拟分析以及优化工作&#xff0c;从而明显提升工作效率与项目质量。然而&#xff0c;资源总是有限的&#xff0c;…