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;
    }
};
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值