map 和 set 的介绍和简单使用

news2024/11/23 22:47:18

目录

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

2. 键值对

2.1. make_pair

3. 树形结构的关联式容器

3.1. set (Key 模型)

3.1.1. std::set::find 和 std::set::count

3.2. map (Key-Value 模型)

3.2.1. std::map::insert

3.2.2. std::map::operator[]

3.3. multiset

3.4.1. std::multiset::find

3.4.2. std::multiset::count

3.4. multimap

3.5. 关于map和set的练习题

3.5.1. 前K个高频单词

3.5.2.  两个数组的交集


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

在之前我们已经学过了一些容器,例如string、vector、list等它们都被称之为序列式容器。

那什么叫做序列式容器?什么又叫做关联式容器呢?

  • 序列式容器:
    • 序列式容器是指一种数据结构其底层为线性序列的数据结构,里面存储的是元素本身;
    • 序列式容器的特点是元素之间有固定的次序关系,并且可以根据位置进行访问。常见的序列式容器包括string,vector,list,deque等;
    • 这些容器在使用时可以根据需要进行插入、删除、查找等操作,还可以使用迭代器来遍历容器中的元素;

    • 序列式容器提供了不同的访问和操作方式,满足不同场景下的需求。

  • 关联式容器:
    • 关联式容器是指一种数据结构,用于存储和管理以关键字(Key)为主要标识的数据元素。与序列式容器不同,关联式容器的元素是按照关键字 (Key) 进行组织和访问的;
    • 关联式容器的底层通常使用红黑树(Red-Black Tree)等高效的数据结构来实现,在插入、删除、查找等操作上具有较高的效率。它们提供了基于关键字的访问和操作能力;
    • 常见的关联式容器包括集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)等。集合和多重集合存储唯一的关键字(Key),映射和多重映射存储关键字-值(Key-Value)对(也被称之为键值对);
    • 关联式容器可以根据关键字(Key)进行快速的查找,使得数据的访问更加高效。同时,关联式容器往往还提供了排序、去重等功能,适用于需要按照关键字进行检索和排序的场景。

2. 键值对

键值对(Key-Value Pair)是一种数据表示方式,用于将一个值(Value)与一个唯一标识符(Key)相关联,我们可以通过 Key 快速找到 Value。

在键值对中,键(Key)用于唯一标识和索引值,而值(Value)则是与该键关联的数据。通过使用键作为索引,可以快速地访问和操作与之相关联的值。

键值对可以用于表示和管理各种数据,在关联式容器中,键值对经常被使用。例如:

  • 在映射(map)中,每个键对应一个唯一的值(这里的键具有唯一性);
  • 在多重映射(Multimap)中,一个键可以对应多个值(即允许相同的键值)。

通过键值对的方式,我们可以方便地访问和操作数据。

而在 SGI-STL (Standard Template Library) 中定义了名为 std::pair 的键值对结构,其定义在 <utility> 中,std::pair 是一个类模板,其定义如下:

template <class T1, class T2>
struct pair 
{
    typedef T1 first_type;   // 键类型
    typedef T2 second_type;  // 值类型

    T1 first;   // 键(Key)
    T2 second;  // 值(Value)

    // 构造函数
    pair()
    :first(T1())
    ,second(T2())
    {}
    
    pair(const T1& x, const T2& y)
    :first(x)
    ,second(y)
    {}  

    template <class U, class V>
    pair(const pair<U, V>& p)
    :first(p.first)
    ,seconde(p.second)
    {}   
};

pair 类模板具有两个模板参数,分别表示键 (Key) 的类型(T1)和值 (Value) 的类型(T2),它包含了两个公有成员变量 first 和 second,分别用于存储键和值。

pair 提供了多个构造函数,使得可以方便地创建和初始化键值对对象。除了默认构造函数外,还有一个接受键和值作为参数的构造函数,以及一个模板构造函数,允许从其他类型的 pair 对象转换。

通过使用 pair,我们可以方便地组织和操作键值对数据。在STL的各种关联式容器中,经常使用 pair 来表示键值对,以实现高效的数据存储和访问。

2.1. make_pair

make_pair 是一个 STL(标准模板库)中的函数模板,用于创建一个 pair<T1, T2> 对象,make_pair 函数模板允许你通过传递两个值作为参数,创建一个 pair<T1, T2> 匿名对象,并自动推断出适当的类型。

下面是它的一种简单实现示例:

template <class T1, class T2>
pair<T1, T2> make_pair(T1 t, T2 u) 
{
    return pair<T1, T2>(t, u);
}

上述示例只是 make_pair 一种常见的实现方式,但实际中的实现可能会有所不同。在编写代码时,通常可以直接使用 <utility> 头文件中的 make_pair,而不需要关心其具体实现细节。

3. 树形结构的关联式容器

根据应用场景的不同,STL 总共实现了两种不同结构的关联式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树 (即红黑树) 作为其底层结构,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

3.1. set (Key 模型)

set 具体如下:

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

set 是一种集合模型(Set Model),它是关联式容器的一种。

集合模型是一种数据结构,用于存储一组唯一值的集合,并提供高效的插入、删除和查找操作。

在集合模型中,每个元素都是唯一的(Key值是唯一的),不会存在重复值。集合模型通常基于某种平衡树 (例如红黑树) 实现,以确保高效的查找和去重功能。

C++ 标准库中的 set 是一种基于红黑树(Red-Black-Tree)的关联式容器。C++中的 set 以红黑树的方式存储唯一值,并提供了一系列的操作,如插入新元素、删除指定元素、查找特定元素等。由于使用了红黑树作为底层数据结构,set 在插入和删除操作时能够维持自动的排序和平衡,具有较快的查找性能。

set 提供了一系列的成员函数和算法,用于操作和访问集合中的元素。它的特点是按照升序排列元素,并且每个元素都是唯一的。如果需要存储多个相同值的元素,可以使用 multiset 容器。set 在各种场景中都有广泛的应用,例如去重、排序、查找等。

在 C++ 的 set 容器中,元素的键值是不可修改的。这是因为 set 是基于红黑树实现的,红黑树中的每个元素节点的键值是用于按照特定顺序进行排序和比较的,保持元素的有序性。

一旦将一个元素插入到 set 中,它的键值就不能再被修改。如果需要修改元素的键值,你需要先将该元素从 set 中删除,然后修改其值,最后再将修改后的元素重新插入到 set 中。

测试一:验证 std::set 中的 Key 是否具有唯一性和它的排序性, demo 如下:

void Test1(void)
{
	std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
	std::set<int> s;
	for (auto e : v)
		s.insert(e);

	std::set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		std::cout << *it << " ";
		++it;
	}
	std::cout << std::endl;
}

结果如下: 

我们发现,std::set 中的元素的确没有重复, 且迭代器遍历的顺序默认是升序,即 set 的作用:去重 + 排序。

测试二:set 中的 Key 值是否可以被修改,测试 demo 如下:

void Test2(void)
{
	std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
	std::set<int> s;
	for (auto e : v)
		s.insert(e);

	std::set<int>::iterator it = s.begin();

	*it = 10;
}

现象如下:

可以看到,std::set 中的元素的键值是不可修改的。

3.1.1. std::set::find 和 std::set::count

/*
* template < class T,                        
* class Compare = less<T>,        
* class Alloc = allocator<T>      
* > class set;
* value_type: The first template parameter (T)
* size_type: usually the same as size_t
*/
iterator find (const value_type& val) const;
size_type count (const value_type& val) const;

set::find() 可以判断某个Key在不在:

  • 找到了,返回当前迭代器的位置;
  • 没找到,返回end()。

set::count() 也可以判断某个Key在不在:

  • 如果存在,返回1,
  • 不存在,返回0。

测试三:测试 std::set::find,demo 如下:

void Test3(void)
{
	std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
	std::set<int> s;
	for (auto e : v)
		s.insert(e);

	int x = 0;
	while (1)
	{
		std::cin >> x;
		std::set<int>::iterator pos = s.find(x);
		//找到了返回对应迭代器,没找到返回end()        
		if (pos != s.end())
			std::cout << "找到了" << std::endl;
		else
			std::cout << "没找到" << std::endl;
	}
}

测试如下:

测试四: 测试count,demo 如下:

void Test4(void)
{
	std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
	std::set<int> s;
	for (auto e : v)
		s.insert(e);

	int x = 0;
	while (1)
	{
		std::cin >> x;
		size_t ret = s.count(x);
		// 找到了返回1, 没找到返回0
		if (ret)
			std::cout << "找到了" << std::endl;
		else
			std::cout << "没找到" << std::endl;
	}
}

测试现象如下: 

其他的接口,就没必要在举例了,和之前的使用大差不差,更何况,我们还要模拟实现,到时候就更清楚了。 

3.2. map (Key-Value 模型)

  • key:键值对中 Key 的类型;
  • T: 键值对中 Value 的类型;
  • Compare: 比较器的类型,map 中的元素是按照 key 来比较的,缺省情况下按照小于来比较:
    • 对于内置类型而言,一般情况下,该参数不需要传递;
    • 对于自定义类型来说,如果无法比较,需要用户自己显式传递比较规则 (一般情况下按照函数指针或者仿函数来传递,此时传递的是类型)。
  • Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器。
  • 注意:在使用 map 时,需要包含头文件 <map> 。

map 是 C++ STL(标准模板库)中的一个关联容器,它提供了一种将键(key)与值(value)关联起来的方式。

map 类似于字典,它由一系列键值对组成。每个键与一个对应的值相关联,这样就可以通过键 (Key) 来快速查找和访问对应的值 (Value)。

map 中的键是唯一的,每个键对应一个值,map 内部使用红黑树的数据结构来实现,它能够保持键的有序性,这使得 map 在数据查找方面非常高效。对于大量的数据,map 的插入、查找和删除操作的时间复杂度都是 O(log n)。

3.2.1. std::map::insert

/*
* std::map 类模板如下:
* template < class Key,    
* class T,                      
* class Compare = less<Key>,                 
* class Alloc = allocator<pair<const Key, T> >  
* > class map;
* 
* value_type :  pair<const key_type, mapped_type>
* key_type   :  The first template parameter (Key)
* mapped_type:  The second template parameter (T)
*/

// single element (1)	
pair<iterator,bool> insert (const value_type& val);

// with hint (2)	
iterator insert (iterator position, const value_type& val);

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

测试六:测试insert (1),demo 如下:

void Test6(void)
{
	std::map<std::string, std::string> dict;

	dict.insert(std::pair<std::string, std::string>("current", "当前的"));
	dict.insert(std::pair<std::string, std::string>("note", "注意"));
	// 但是我们一般不使用上面的方式(通过构造pair的匿名对象)
	// 我们一般使用std::make_pair
	// make_pair会根据我们传入的参数自动构造pair的匿名对象并返回
	dict.insert(std::make_pair("delete", "删除"));
	dict.insert(std::make_pair("segmentation", "分割"));
	dict.insert(std::make_pair("exist", "存在"));

	// map支持插入相同的Key吗?
	dict.insert(std::make_pair("note", "笔记"));

	// 如何遍历 ?
	std::map<std::string, std::string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		// Note: 如果这里直接对迭代器进行解引用,那么会调用迭代器的operator*,返回的是一个pair的对象
		// 因此在这里要以下面的方式取 pair 的first(Key)和second(Value)
		//std::cout << (*it).first << ":" << (*it).second << std::endl;
		// 但是,我们并不喜欢上面的方式(太麻烦了),我们直接调用迭代器的operator->,返回的是pair对象的地址
		// 因此此时在用->就可以取到pair的first(Key)和second(Value)
		// 但是为了代码的可读性,编译器省略了这个->
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
	std::cout << std::endl;
}

测试结果如下: 

从结果来看,std::map 不支持插入相同的Key(map不支持键值冗余)。

当使用 std::map 的 insert 函数插入一个键值对时,如果插入的键已经存在,则 insert 不会进行插入操作,而会保持不变,并返回一个迭代器 (指向已存在的键值对)。

具体地说,如果插入的键已经在 std::map 中存在,insert 函数将返回一个指向已存在键值对的迭代器,而不会插入新的键值对。如果插入的键是新的,则会将该键值对插入 map 中,并返回指向新插入键值对的迭代器。

因此可以认为:insert 有插入+修改和查找的功能:

  • 当 insert 一个未存在的Key就是插入Key+修改Value。
  • 当 insert 一个已存在的Key,通过返回值(pair<iterator,bool>)的first可以得到这个已经存在的Key的迭代器,可以起到查找的作用。

测试七: 测试 insert (2),统计动物的个数,利用 map::insert,如果存在该动物就++Value,不存在就直接插入即可, demo 如下:

void Test7(void)
{
	std::map<std::string, int> get_count;
	std::string str[] = { "老虎", "狮子", "大熊猫", "长颈鹿", "孔雀" };
	srand((unsigned int)time(nullptr));
	for (size_t i = 0; i < 10; ++i)
	{
		std::string tmp = str[rand() % 5];
		std::map<std::string, int>::iterator pos = get_count.find(tmp);
		// 如果该动物没存在,就插入map中,并将Value赋值为1
		if (pos == get_count.end())
			get_count.insert(make_pair(tmp, 1));
		// 如果该动物存在,将Value值++即可
		else
			++pos->second;
	}
	auto it = get_count.begin();
	while (it != get_count.end())
	{
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
	std::cout << std::endl;
}

现象如下: 

3.2.2. std::map::operator[]

/*
* std::map 类模板如下:
* template < class Key,    
* class T,                      
* class Compare = less<Key>,                 
* class Alloc = allocator<pair<const Key, T> >  
* > class map;
*
* value_type :  pair<const key_type,mapped_type>
* key_type   :  The first template parameter (Key)
* mapped_type:  The second template parameter (T)
*/
pair<iterator,bool> insert (const value_type& val);

mapped_type& operator[] (const key_type& k);

// A call to this function is equivalent to:
(*((this->insert(make_pair(k,mapped_type()))).first)).second

表达式 (*((this->insert(std::make_pair(k, mapped_type()))).first)).second 可以被理解为在 std::map 中插入一个键值对,并返回插入的键所对应的值。

让我们逐步解释这个表达式:

  1. 首先,std::make_pair(k, mapped_type()) 使用键 k 和默认构造的值 mapped_type() (假设 mapped_type 是键 k 对应的值类型的占位符)创建一个新的键值对pair;
  2. 然后,this->insert(std::make_pair(k, mapped_type())) 将上述键值对插入到当前的 std::map 对象中。这个 insert 操作返回一个 std::pair 对象(这个pair对象的first和second分别是:iterator,bool),其中的 first 成员表示插入的迭代器位置,second 成员表示是否进行了插入操作(true 表示插入成功,false 表示键已存在);
  3. 接着,*((this->insert(std::make_pair(k, mapped_type()))).first) 解引用插入操作返回的迭代器,即得到插入的键值对;
  4. 最后,(*((this->insert(std::make_pair(k, mapped_type()))).first)).second 取得插入的键值对中值 (Value) 所在的部分,即当前插入的键所对应的值。

这个表达式的目的是获取插入的键 (Key) 对应的值 (Value),无论是已经存在的键还是新插入的键。它的效果等同于使用 [] 运算符来访问键对应的值,例如 map[key],通过这个表达式,可以确保插入操作执行后,可以立即获取到插入的键所对应的值 (Value),即使该键 (Key) 已存在。

insert Key 时:

  • 如果这个 Key 已经存在,会返回 <之前已经存在的Key的迭代器, false> ;
  • 如果这个 Key 不存在,会返回<插入Key的迭代器, true>。

operator[] 的功能:

  1. 如果这个Key不存在,插入 <Key, Value>;

  2. 如果这个Key存在,可以修改Value,因为返回的是 Value的引用;

  3. 如果这个Key不存在,插入 <Key, Value> ,相当于 插入 Key,修改 Value;

  4. 如果这个Key存在,且不修改Value,就相当于查找的动作。

根据上面的理解,我们可以通过 operator[] 来实现统计动物个数的代码,具体如下:

void Test8(void)
{
	std::map<std::string, int> get_count;
	std::string str[] = { "老虎", "狮子", "大熊猫", "长颈鹿", "孔雀" };
	srand((unsigned int)time(nullptr));
	for (size_t i = 0; i < 10; ++i)
	{
		std::string tmp = str[rand() % 5];
		std::map<std::string, int>::iterator pos = get_count.find(tmp);
		// 如果该动物没存在,就插入map中,并将Value赋值为1
		if (pos == get_count.end())
			get_count.insert(make_pair(tmp, 1));
		// 如果该动物存在,将Value值++即可
		else
			++pos->second;
	}
	auto it = get_count.begin();
	while (it != get_count.end())
	{
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
	std::cout << std::endl;
}

现象如下:

我们上面的字典 (dict) 也可以用operator[]来实现:

void Test9(void)
{
	std::map<std::string, std::string> dict;
	dict.insert(std::pair<std::string, std::string>("current", "当前的"));
	dict.insert(std::pair<std::string, std::string>("note", "注意"));

	// 但是我们一般不使用上面的方式(通过构造pair的匿名对象)
	// 我们一般使用std::make_pair

	dict.insert(std::make_pair("delete", "删除"));
	dict.insert(std::make_pair("segmentation", "分割"));
	dict.insert(std::make_pair("exist", "存在"));

	// 我们采取 operaotr[] 完成 插入、修改、查找: 

	dict["step"];              // "step" 这个Key不存在,直接插入Key
	dict["step"] = "步骤";     // "step" 这个Key存在,修改Value
	dict["patience"] = "耐心"; // "patience" 这个Key不存在,通过返回值的引用实现 插入Key + 修改Value
	std::cout << "exist: " << dict["exist"] << std::endl;  //注意:当要查找的Key是存在的才是查找, otherwise,就是插入了

	auto it = dict.begin();
	while (it != dict.end())
	{
		std::cout << it->first << ":" << it->second << std::endl;
		++it;
	}
	std::cout << std::endl;
}

现象如下:

有了map,我们之前遇到的一些问题,现在就简单多了,例如,下面的这道题:原题连接如下:138. 随机链表的复制 - 力扣(LeetCode)

以前我们的做法就是分为三步:

  1. 遍历原表,复制原表节点,尾插到原节点的后面;
  2. 根据原节点,处理 random ;
    1. 如果原表中节点的 random 为 nullptr,那么新表中的节点的 random 也为 nullptr;

    2. 根据原节点位置,得到我们需要的random。   

  3. 建立新表,恢复原表。

而现在我们提出了新的解法:

  1. 遍历原表,复制一个新表;
  2. 将原表和新表的节点 insert 进map<Node*,Node*>中;
  3. 新表的random就是对应map的Key的random的second。

代码如下:

class Solution {
public:
    Node* copy_list(Node* head)
    {
        Node* newhead = nullptr;
        Node* newtail = nullptr;
        while(head)
        {
            if(newhead == nullptr)
            {
                newhead = newtail = new Node(head->val);
            }
            else
            {
                newtail->next = new Node(head->val);
                newtail = newtail->next;
            }
            head = head->next;
        }
        return newhead;
    }
    Node* copyRandomList(Node* head) {
        if(!head)
            return nullptr;
        // 1、建立新表
        Node* newhead = copy_list(head);
     
        // 2、将旧表和新表insert到node_map中
        // 即Key就是旧表节点,Value是新表节点
        std::map<Node*,Node*> node_map;
        Node* cur = head;
        Node* tmp = newhead;
        while(cur != nullptr)
        {
            node_map[cur] = tmp;
            cur = cur->next;
            tmp = tmp->next;
        }
        // 3、通过map的Key-Value处理新表的random
        Node* ret = head;
        while(ret)
        {
            auto pos = node_map.find(ret);
            if(pos != node_map.end())
            {
                // 如果旧表的random == nullptr,那么新表的random也为nullptr
                if(pos->first->random == nullptr)
                {
                    pos->second->random = nullptr;
                }
                // otherwise,新表的random就是对应map的Key的random的second
                else
                {
                   pos->second->random = node_map[ret->random];
                }
            }
            ret = ret->next;
        }
        return newhead;
    }
};

3.3. multiset

 multiset 是 C++ STL(标准模板库)中的一个容器,用于存储一组元素,允许包含相同值的元素(即允许存储相同的Key或者叫允许键值冗余)。

multiset 是基于红黑树实现的,它类似于 set 容器,但允许存储重复的元素。这意味着你可以向 multiset 中插入多个相同的值,并且 multiset 会根据元素值的顺序自动进行排序(默认是升序)。与 set 一样,multiset 也支持快速的插入、删除和查找操作。

multiset 容器可以方便地用于处理需要存储重复元素且有序的情况。它提供了一些有用的成员函数,如 insert(插入元素)、erase(删除元素)、find(查找元素)等,以及一些迭代器相关的操作来遍历容器中的元素。

3.4.1. std::multiset::find

iterator find (const value_type& val) const;

multiset 是一种基于红黑树实现的容器,它允许存储重复的元素,并且会根据键 (Key) 的排序准则来自动对元素进行排序。当进行查找操作时,multiset 会使用红黑树的查找算法,找到中序遍历(LNR)第一个匹配的键。

3.4.2. std::multiset::count

size_type count (const value_type& val) const;

std::multiset::count 用于计算指定键(Key)在 multiset 中的出现次数, 测试 demo 如下:

void Test10(void)
{
	std::vector<int> v{ 6, 2, 4, 3, 1, 2, 6, 4, 3, 8, 7 };
	std::multiset<int> s;
	for (auto e : v)
	{
		s.insert(e);
	}
	size_t cnt = s.count(2);
	if (!cnt)
		std::cout << "没有这个Key" << std::endl;
	else
		std::cout << "Key的cnt: " << cnt << std::endl;
}

结果如下:

3.4. multimap

std::multimap 是 C++ 标准库中的容器之一,它是一个关联容器,允许存储多个相同键(允许键值冗余)的键值对。

std::multimap 类模板提供了一种将键和值关联的方式,它所存储的键值对可以具有相同的键(Key)。这就是与 std::map 的主要区别,std::map 只允许每个键关联一个值,而 std::multimap 允许一个键关联多个值。

为什么 multimap 中没有 operator[] 呢?

原因是,operator[] 通常用于直接访问容器中特定键 (Key) 对应的值 (Value),而对于 std::multimap 这种允许一个键关联多个值的容器来说,使用 operator[] 并不明确应该返回哪个值 (Value),此时的 operator[] 的行为就变得模糊不清。

如果 std::multimap 提供了 operator[] 运算符,那么它将需要决定返回哪个值,这可能会引起混淆。为避免歧义,std::multimap 不提供 operator[]。

如果你想要访问 std::multimap 中特定键对应的所有值,可以使用 equal_range 函数或迭代器来获取一个表示值范围的区间,然后通过遍历该区间来访问所有的值。

3.5. 关于map和set的练习题

3.5.1. 前K个高频单词

链接:692. 前K个高频单词 - 力扣(LeetCode)

思路:先统计每个单词出现的个数(建立单词和单词个数的联系),题干要求,如果K相等,需要按照字典顺序排序,因此要控制比较逻辑。

第一种思路:

  1. 用 map<string,int> 统计每个单词出现的次数;
  2. 将 map<string,int> 中的数据导入 vector<pair<string,int>> 中;
  3. 用标准库的sort(sort只支持随机迭代器),当然还需要我们控制比较逻辑。

第二种思路:

  1. 用 map<string,int> 统计每个单词出现的次数;
  2. set<std::pair<string,int>,comapre> 因为set可以传递仿函数,因此我们可以通过set控制比较逻辑。

代码如下: 

//第一种思路
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        struct compare
        {
            bool operator()(const std::pair<std::string,int>& kv1,const std::pair<std::string,int>& kv2) const
            {
                return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
            }
        };
        // 第一步:计算单词出现的个数
        std::map<std::string,int> count_map;
        for(auto& e : words)
        {
            ++count_map[e];
        }
        // 第二步: 将数据导入vector中
        std::vector<std::pair<string,int>> v(count_map.begin(),count_map.end());

        // 第三步: 排序,需要控制比较逻辑,个数不相等,大的在前;个数相等,按照字典顺序排序
        std::sort(v.begin(),v.end(),compare());
        std::vector<std::string> ret;
        for(size_t i = 0; i < k; ++i)
        {
            ret.push_back(v[i].first);
        }
        return ret;
    }
};

//第二种思路:
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        struct compare
        {
            bool operator()(const std::pair<std::string,int>& kv1,const std::pair<std::string,int>& kv2) const
            {
                return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
            }
        };
        // 第一步:计算单词出现的个数
        std::map<std::string,int> count_map;
        for(auto& e : words)
        {
            ++count_map[e];
        }
        // set可以显示传递仿函数,利用仿函数的比较逻辑
        std::set<pair<std::string,int>,compare> s(count_map.begin(),count_map.end());

        std::vector<std::string> ret;

        auto pos = s.begin();
        while(k--)
        {
            ret.push_back(pos->first);
            ++pos;
        }
        return ret;
    }
};

3.5.2.  两个数组的交集

链接:349. 两个数组的交集 - 力扣(LeetCode)

思路如下:

找交集 (需要先排序+去重):

  1. 不相等,小的++;
  2. 相等,就是交集,同时++;
  3. 一个集合走完就结束。

找差集 (先排序):

  1. 相等,同时++;
  2. 不相等,小的就是差集,小的++;
  3. 一个集合走完了,剩下没有走完的集合的值也是差集。

代码如下:

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

        // 根据交集的求解方法
        auto pos1 = s1.begin();
        auto pos2 = s2.begin();
        std::vector<int> ret;
        while(pos1 != s1.end() && pos2 != s2.end())
        {
            // 小的++
            if(*pos1 < *pos2)
                ++pos1;
            else if(*pos1 > *pos2)
                ++pos2;
            // 相等就是交集
            else
            {
                ret.push_back(*pos1);
                ++pos1;
                ++pos2;
            }
        }
        return ret;
    }
};

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

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

相关文章

Multisim 14二极管及其电路的应用

multisim multisim&#xff0c;即电子电路仿真设计软件。Multisim是美国国家仪器&#xff08;NI&#xff09;有限公司推出的以Windows为基础的仿真工具&#xff0c;适用于板级的模拟/数字电路板的设计工作。它包含了电路原理图的图形输入、电路硬件描述语言输入方式&#xff0…

周末可以做什么副业?

周末可以做很多种副业&#xff0c;具体可以根据个人兴趣和技能来选择。以下是一些常见的周末副业推荐 1. 线上销售 可以开设自己的网店&#xff0c;销售自己制作的产品、代理热门商品或者利用二手交易平台售卖闲置物品。 2. 做任务 空闲时间可以选择做的是百度的黑鲨阁&…

^_^填坑备忘^_^C#自动化编程实现STK+Exata对卫星互联网星座进行网络仿真

C#实际选择 STK11版本 or STK12版本的问题备注。 【C#自动化客户端调用STK时&#xff0c;实际选择 STK11版本 or STK12版本 的调试运行备注】 以下代码“更新并重新打包备份为”〔testSTKQualNetInterface备份08.1_★避坑★【种子卫星&#xff1a;天线直接安装在卫星上&#…

【刷题篇】滑动窗口(二)

文章目录 1、水果成篮2、找到字符串中所有字母异位词3、串联所有单词的子串4、最小覆盖子串 1、水果成篮 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多…

JupyterLab OpenCV展示图片

JupyterLab OpenCV展示图片 方式一 注意&#xff1a;此种方式如果在远程服务器上的JupyterLab上运行&#xff0c;可能会出现错误。 import cv2# 读取图片 image cv2.imread(photo/blg.png)# 显示图片 cv2.imshow(image, image)# 等待按键&#xff0c;之后关闭所有窗口 cv2.w…

Jsp+Servlet实现图片上传和点击放大预览功能(提供Gitee源码)

前言&#xff1a;在最近老项目的开发中&#xff0c;需要做一个图片上传和点击放大的功能&#xff0c;在Vue和SpringBoot框架都有现成封装好的组件和工具类&#xff0c;对于一些上世纪的项目就没这么方便了&#xff0c;所以需要自己用原生的代码去编写&#xff0c;这里分享一下我…

MySQL性能优化(提升数据库性能的措施)

万物皆有裂痕&#xff0c;那是光照进来的地方。大家好&#xff0c;今天给大家分享一下关于MySQL性能优化&#xff0c;在处理大型数据集和高负载情况下&#xff0c;MySQL数据库的性能优化是至关重要的。通过合理的调优策略&#xff0c;可以有效提高数据库的响应速度和稳定性。本…

5.10.3 使用 Transformer 进行端到端对象检测(DETR)

框架的主要成分称为 DEtection TRansformer 或 DETR&#xff0c;是基于集合的全局损失&#xff0c;它通过二分匹配强制进行独特的预测&#xff0c;以及 Transformer 编码器-解码器架构。 DETR 会推理对象与全局图像上下文的关系&#xff0c;以直接并行输出最终的预测集。 1. …

WEB后端复习——javabean与会话cookie、session

JavaBean 是一种符合特定命名约定的 Java 类&#xff0c;它通常用于封装数据。 JavaBean 的主要特点是&#xff1a; 1. 无参构造器&#xff1a;JavaBean 必须有一个公共的&#xff08;public&#xff09;无参构造方法&#xff0c;以便于反射时能够创建对象实例。 2. 属性&…

【Linux网络】Https【下】{CA认证/证书的签发与认证/安全性/总结}

文章目录 1.引入证书【为方案五铺垫】1.1再谈https1.2SSL/TLS1.3CA机构1.4理解数字签名1.4继续铺垫1.5方案五服务端申请证书回顾一二三回顾方案四方案五过程寻找方案五的漏洞客⼾端对证书进⾏认证 2.查看证书2.1查看浏览器的受信任证书发布机构2.2中间⼈有没有可能篡改该证书2.…

nodeJs用ffmpeg直播推流到rtmp服务器上

总结 最近在写直播项目 目前比较重要的点就是推拉流 自己也去了解了一下 ffmpeg FFmpeg 是一个开源项目&#xff0c;它提供了一个跨平台的命令行工具&#xff0c;以及一系列用于处理音频和视频数据的库。FFmpeg 能够执行多种任务&#xff0c;包括解封装、转封装、视频和音频…

中霖教育:税务师考试可以申请免试吗?

符合下列相应条件之一的&#xff0c;可报名参加税务师职业资格考试&#xff1a; 1.取得经济学、法学、管理学学科门类大学本科及以上学历(学位);或者取得其他学科门类大学本科学历&#xff0c;从事经济、法律相关工作满1年。 2.取得经济学、法学、管理学学科门类大学专科学历…

【HCIP学习】BGP选路、过滤及属性

一、BGP路由选路原则&#xff08;13条&#xff09; 1、首先丢弃下一跳&#xff08;NEXT_HOP&#xff09;不可达的路由&#xff1b; 2、优选Preferred-value值最大的路由&#xff1b;默认为0&#xff1b; Preferred-value&#xff1a;定义&#xff1a;首选项。 属性值&#…

windows系统安装Ubuntu子系统

安装前先在 控制面板 中打开 程序与功能选项 &#xff0c;点击 启用或关闭Windows功能&#xff1a; 勾选 适用于 Linux的Windows子系统 和 虚拟机平台 、 Hyper-v 。 重启电脑后再 Microsoft Store Windows应用商店 中下载合适的Ubuntu版本。 运行Ubuntu程序&#xff0c;如出现…

在xAnyLabeling中加载自己训练的yolov8s-obb模型进行半自动化标注

任务思路&#xff1a; 先使用xAnyLabeling标注一部分样本&#xff0c;训练出v1版本的yolov8-obb模型&#xff0c;然后加载yolov8-obb模型到xAnyLabeling中对其余样本进行半自动化标注。节省工作量。 任务流程&#xff1a; 1.准备xAnyLabeling标注工具 下载代码&#xff0c;…

[Flutter GetX使用] Getx路由和状态管理-GetController使用过程中的踩坑记录

文章目录 问题 - Get.find() 报错!原因总结A:路由和控制器设计a1:项目中的Get路由aa1.项目路由结构aa2.本项目路由的注意点: B: GetController的冷知识C: 总结来看D: 一些参考资料 问题 - Get.find() 报错! 刚接触Getx, 遇到 Get.find()确找不到, 进而报错的问题, 一时间有点没…

零基础学MySQL

1. 零基础学MySQL 1.1 数据库简介 1.1.1 数据库三层结构 1. 所谓安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database manage system) 2. 一个数据库中可以创建多个表,以保存数据(信息)。 3. 数据…

OpenCompass 大模型评测实战学习笔记

大模型开源开放评测体系 “司南” (OpenCompass2.0)&#xff0c;用于为大语言模型、多模态模型等提供一站式评测服务。其主要特点如下&#xff1a; 开源可复现&#xff1a;提供公平、公开、可复现的大模型评测方案 全面的能力维度&#xff1a;五大维度设计&#xff0c;提供 70…

iLogtail 社区开源之夏活动来了!

作者&#xff1a;玄飏 在这个充满活力的夏日&#xff0c;随着阳光一同灿烂的是开源精神的光辉与创新的火花。iLogtail 社区高兴地宣布&#xff0c;我们正式加入开源之夏 2024 的行列&#xff0c;诚邀每一位怀揣梦想与激情的学生开发者&#xff0c;共同开启一场探索技术前沿、贡…

2024年,Web开发新趋势!

随着我们迈入新的一年&#xff0c;现在正是审视2024年网页开发领域开始流行哪些趋势的绝佳时机。回顾2023年的一系列更新&#xff0c;以下是来年一些热门话题的概览。 自主托管有回归的趋势 近些年&#xff0c;自主托管一直是网页开发者和公司托管其应用程序的默认方式。开发…