map 和 set 的介绍和简单使用

目录

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;
    }
};

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

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

相关文章

[Java EE] 文件IO(一):文件概念与文件系统操作

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

vscode怎么设置背景图片?

vscode背景图片是可以自己设置的&#xff0c;软件安装后默认背景的颜色是黑色的&#xff0c;这是默认的设计&#xff0c;如果要修改背景为指定的图片&#xff0c;那么我们需要安装插件&#xff0c;然后再通过代码来设置背景图片的样式&#xff0c;下面我们就来看看详细的教程。…

大数据交通行政执法监测系统

交通行政执法监测系统应用系统按照监测主体可分为&#xff1a;出租车交通违法监测&#xff0c;客车交通违法监测&#xff0c;货车、危化品车辆交通违法监测&#xff0c;非法营运车辆监测。功能模块涵盖&#xff1a;特征识别、档案查询、预警分析等。 &#xff08;1&#xff09;…

java中EQ、NE、GE、GT、LE、LT

关系运算符 包括EQ、NE、GE、GT、LE、LT几个&#xff0c;关系运算符返回的是真“True”或假“False”。 eq&#xff08;Equal to&#xff09; 等 运算符 &#xff0c;如果运算符两边相同则返回真&#xff0c;否则返回假&#xff1b; ne&#xff08;Not Equal to&#xff09; 不…

力扣HOT100 - 55. 跳跃游戏

解题思路&#xff1a; class Solution {public boolean canJump(int[] nums) {int n nums.length;int maxReach 0;// 正常来说每次至少跳一格&#xff0c;所以最多循环n次for (int i 0; i < n; i) {if (i > maxReach) return false;// 这种情况代表遇到了0&#xff0…

使用train.py----yolov7

准备工作 在训练之前&#xff0c;数据集的工作和配置环境的工作要做好 数据集&#xff1a;看这里划分数据集&#xff0c;训练自己的数据集。_划分数据集后如何训练-CSDN博客 划分数据集2&#xff0c;详细说明-CSDN博客 配置环境看这里 从0开始配置环境-yolov7_gpu0是inter g…

11、关系运算符、逻辑运算符(讲解 和 的区别)、赋值表达式、三目表达式、运算符优先级(超详细版本)+结合性的分析

这里写目录标题 一、关系运算符&#xff08;比较运算符&#xff09;二、⭐逻辑运算符1、 && 和 &2、|| 或 |3、&#xff01;4、^ 三、赋值运算符四、三目运算符&#xff08;条件运算符&#xff09;五、运算符优先级 在讲之前先明确几个概念&#xff1a; 1、单目运算…

【Arduino】ESP32/ESP8266 JSON格式解析

目录 1、JSON 2、JSON语法格式 基本概念&#xff1a; 语法规则&#xff1a; 数据类型&#xff1a; 示例&#xff1a; 3、JSON解析 单一对象JSON解析&#xff08;无嵌套&#xff09; JSON数组解析 使用ArduinoJson官网在线工具解析JSON信息 ESP8266闪存存储的JSON解析…

VScode 修改 Markdown Preview Enhanced 主题与字体

VScode 修改 Markdown Preview Enhanced 主题与字体 1. 修改前后效果对比2. 修改主题2.1 更改默认主题2.2 修改背景色 3. 修改字体 VS Code基础入门使用可查看&#xff1a; VS Code 基础入门使用&#xff08;配置&#xff09;教程 其他Vs Code 配置可关注查看&#xff1a; Vs C…

ElasticSearch 与 OpenSearch:拉开性能差距

Elasticsearch 与 OpenSearch&#xff1a;扩大性能差距 对于任何依赖快速、准确搜索数据的组织来说&#xff0c;强大、快速且高效的搜索引擎是至关重要的元素。对于开发人员和架构师来说&#xff0c;选择正确的搜索平台可以极大地影响您的组织提供快速且相关结果的能力。在我们…

docker(二):Centos安装docker

文章目录 1、安装docker2、启动docker3、验证 官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 1、安装docker 下载依赖包 yum -y install gcc yum -y install gcc-c yum install -y yum-utils设置仓库 yum-config-manager --add-repo http://mirrors…

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

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

Linux 第二十八章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

利用PS在不伤背景的前提下根据颜色去除图像上不想要的内容

下面为一个例子&#xff0c;去除图像上红色的虚线 Step1.用套索工具框选带有颜色的部分 Step2.切换到魔术棒工具&#xff0c;上端选项中&#xff0c;点击与选区交叉&#xff0c;连续这一项不要勾选 Step3.在需要去除的部分点击一下即可在框选范围内选中所有同颜色的区域&#x…

【IC前端虚拟项目】验证环境env与base_teat思路与编写

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 上一篇里解决了最难搞的axi_ram_model,接下来呢就会简单又常规一些了,比如这一篇要说的env和base_test的搭建。在这里我用了gen_uvm_tb脚本: 【前端验证】验证自动化脚本的最后一块拼图补全——gen_t…

Cross-Image Attention for Zero-Shot Appearance Transfer——【代码复现】

本文发表于SIGGRAPH 2024&#xff0c;是一篇关于图像编辑的论文&#xff0c;Github官网网址如下&#xff1a; garibida/cross-image-attention&#xff1a; “Cross-Image Attention for Zero-Shot Appearance Transfer”的正式实现 (github.com) 一、基本配置环境准备 请确保…

数组实现循环队列

1、分析 循环队列最主要的特点为当前面的空间被pop后&#xff0c;后面的数据可以插入到前面空余的数据中去&#xff1b; 所以最难的部分为判断什么时候为空什么时候为满&#xff1a; a、空满问题 我们先来分析当数据满时&#xff0c;head和tail相等&#xff08;tail认为是指…

架构设计之学新而知故

缘由 因为一些特殊的机缘&#xff0c;接触到洋葱架构等一些新架构设计概念。 尝试理解了一段时间&#xff0c;就想简单梳理下对它们的理解&#xff0c;以达到学新而知故 &#x1f603; 信息增益 以前计算机专业并不设置通信领域的信息论的专业课程&#xff0c;但是&#xf…

条件变量解决同步问题之打印金鱼

说明 本代码为jyy老师上课演示条件变量解决同步问题示例(本人只做记录与分享) 本人未使用老师封装的POSIX线程库, 直接在单文件中调试并注释 问题描述 有三类线程 T1 若干: 死循环打印< T2 若干: 死循环打印> T3 若干: 死循环打印_ 任务: 对线程同步&#xff0c;使得屏幕…

eNSP中小型园区网络拓扑搭建(下)

→b站直通车&#xff0c;感谢大佬← →eNSP中小型园区网络拓扑搭建&#xff08;上&#xff09;← 不带配置命令的拓扑图已上传~ 配置ospf SW5 # ospf 1 router-id 5.5.5.5area 0.0.0.0network 192.168.51.5 0.0.0.0network 192.168.52.5 0.0.0.0area 0.0.0.10network 192.1…