欢迎来到我的频道 点击跳转专栏
文章目录
- [1. map类的介绍](#1. map类的介绍)
- [1.1 pair类型的介绍](#1.1 pair类型的介绍)
- [0. pair 核心介绍](#0. pair 核心介绍)
- [1. 核心特性](#1. 核心特性)
- [2. 基础用法示例](#2. 基础用法示例)
- [3. pair与 map 的核心关系(接口部分后面会细写)](#3. pair与 map 的核心关系(接口部分后面会细写))
- [4. 注意](#4. 注意)
- [1.2 map的构造](#1.2 map的构造)
- [1. 默认构造](#1. 默认构造)
- [2. 拷贝构造](#2. 拷贝构造)
- [3. 移动构造(C++11)](#3. 移动构造(C++11))
- [4. 范围构造](#4. 范围构造)
- [5. 初始化列表构造(最常用,C++11)](#5. 初始化列表构造(最常用,C++11))
- [1.3 map的迭代器和常规接口](#1.3 map的迭代器和常规接口)
- [1.4 修改类接口(map的增删查等)](#1.4 修改类接口(map的增删查等))
- [1. insert:插入元素](#1. insert:插入元素)
- [2. erase:删除元素](#2. erase:删除元素)
- [3. swap:交换内容](#3. swap:交换内容)
- [4. clear:清空内容](#4. clear:清空内容)
- [5. emplace(C++11+):构造并插入元素](#5. emplace(C++11+):构造并插入元素)
- [6. emplace_hint(C++11+):带位置提示的构造插入](#6. emplace_hint(C++11+):带位置提示的构造插入)
- [1.5 equal_range](#1.5 equal_range)
- [1. 核心作用](#1. 核心作用)
- [2. 用法示例](#2. 用法示例)
- [3. 与 find/lower_bound/upper_bound的关系](#3. 与 find/lower_bound/upper_bound的关系)
- [1.6 map的修改获取数据](#1.6 map的修改获取数据)
- 1.operator[]
- 2.例子
- [3. 原理](#3. 原理)
- [4. 实际效果](#4. 实际效果)
- [2. multimap](#2. multimap)
- [2.1 基本特性](#2.1 基本特性)
- [2.1 常用成员函数](#2.1 常用成员函数)
- [1. 插入元素](#1. 插入元素)
- [2. 查找元素(关键!)](#2. 查找元素(关键!))
- [2.2 为什么没有 operator[]?](#2.2 为什么没有 operator[]?)
- [2.3 完整示例](#2.3 完整示例)
- [2.4 总结](#2.4 总结)
- 3.例题
1. map类的介绍
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较 ,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是 O ( l o g N ) O(logN) O(logN),迭代器遍历是走的中序,所以是按key有序顺序遍历的。

1.1 pair类型的介绍
map底层的红黑树节点中的数据,使用pair<Key, T>存储键值对数据。
0. pair 核心介绍
std::pair是 C++ 标准库<utility>头文件中提供的二元组模板类,用于将两个任意类型的值"绑定"成一个整体,是处理"键值对""二元数据"的基础工具。
1. 核心特性
-
模板参数 :
template <class T1, class T2> struct pair,T1/T2可以是任意类型(基础类型、自定义类型、容器等)。 -
成员变量 :
first:第一个值(通常对应map的key);second:第二个值(通常对应 map 的value);
均为公有成员,可直接访问。
-
常用构造方式 :
cpp#include <utility> #include <string> // 方式1:默认构造(值默认初始化) std::pair<int, std::string> p1; // p1.first=0, p1.second="" // 方式2:直接初始化 走构造函数 std::pair<int, std::string> p2(101, "张三"); // 方式3:std::make_pair(自动推导类型,推荐) auto p3 = std::make_pair(102, "李四"); // 方式4:列表初始化(C++11+) std::pair<int, std::string> p4{103, "王五"}; -
核心功能 :支持赋值、比较(默认按
first比较,first相等则比较second)、C++17 结构化绑定等。
2. 基础用法示例
cpp
#include <iostream>
#include <utility>
#include <string>
int main() {
std::pair<int, std::string> student(101, "张三");
// 访问成员
std::cout << "学号:" << student.first << ",姓名:" << student.second << std::endl;
// 结构化绑定(C++17)
auto [id, name] = student;
std::cout << "解构:" << id << " " << name << std::endl;
// 比较
std::pair<int, std::string> p1(1, "a"), p2(2, "b"), p3(1, "c");
std::cout << (p1 < p2) << std::endl; // 1(true,first 1<2)
std::cout << (p1 < p3) << std::endl; // 1(true,first相等,second "a"<"c")
return 0;
}
3. pair与 map 的核心关系(接口部分后面会细写)
std::map 本质是存储键值对的有序关联容器 ,其底层(红黑树)的每个节点都存储一个 std::pair<const Key, T>,二者是"容器存储单元"与"容器"的强依赖关系,具体体现在以下维度:
| 维度 | 具体关联 |
|---|---|
| 存储载体 | map 的每个元素都是 std::pair<const Key, T>: - first:const 修饰的键(保证键不可修改,符合 map 键唯一且有序的特性); - second:可修改的值。 |
| 初始化依赖 | map 初始化列表的底层是接收一组 pair: std::map<int, string> m = {``{1, "a"}, {2, "b"}} (本质是隐式类型转换)等价于 {make_pair(1,"a"), make_pair(2,"b")}。 |
| 遍历/访问返回值 | 遍历 map 时,迭代器解引用结果是 pair<const Key, T>&;map::find() 返回的迭代器也指向 pair。 |
| 插入操作 | map::insert() 接收 pair<Key, T> 作为参数(或 emplace 直接构造 pair): m.insert(make_pair(3, "c")) / m.emplace(3, "c")。 |
4. 注意
- map 中 pair 的键是 const :
由于map底层红黑树的有序性依赖键,因此 map 存储的pair中first(键)被const修饰,无法直接修改;若要修改键,需先删除旧键值对,再插入新的。 - pair 是 map 操作的基础单元 :
map的insert/emplace/equal_range等接口均围绕pair设计,比如insert的返回值是pair<iterator, bool>(迭代器指向插入/已存在的元素,bool表示是否插入成功)。 - pair 与 map 排序的关联 :
map 按键排序的本质是对pair<const Key, T>的first进行比较;若自定义键类型,需保证pair能通过first完成比较(重载<或传入仿函数)。
1.2 map的构造
map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
1. 默认构造
构造一个空的 map,底层红黑树无节点,仅初始化排序规则(默认 less<Key>)和分配器。
cpp
#include <map>
#include <string>
int main() {
// 默认构造:空 map,键 int,值 string,默认升序排序
std::map<int, std::string> empty_map;
// 验证空:empty() 返回 true
std::cout << "是否为空:" << (empty_map.empty() ? "是" : "否") << std::endl; // 输出:是
return 0;
}
2. 拷贝构造
逐拷贝源 map 的所有键值对(底层红黑树深拷贝),生成一个与源 map 完全相同的新 map(排序规则、元素均一致)。
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> src_map = {{1, "a"}, {2, "b"}};
// 拷贝构造
std::map<int, std::string> copy_map(src_map);
// 遍历验证:与 src_map 完全一致
for (const auto& p : copy_map) {
std::cout << p.first << ":" << p.second << " "; // 输出:1:a 2:b
}
return 0;
}
3. 移动构造(C++11)
"窃取" 源 map 的底层红黑树资源(指针、节点等),源 map 会变为空(浅拷贝 + 资源转移),效率远高于拷贝构造(无内存重新分配)。
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> src_map = {{1, "a"}, {2, "b"}};
// 移动构造(std::move 标记为右值)
std::map<int, std::string> move_map(std::move(src_map));
std::cout << "移动后源 map 是否为空:" << (src_map.empty() ? "是" : "否") << std::endl; // 是
for (const auto& p : move_map) {
std::cout << p.first << ":" << p.second << " "; // 输出:1:a 2:b
}
return 0;
}
4. 范围构造
遍历 [first, last) 迭代器区间的元素(需是 pair<Key, T> 或可转换为该类型 ),将其插入新 map,自动按排序规则去重、排序。
cpp
#include <iostream>
#include <map>
#include <vector>
#include <string>
int main() {
// 先准备一个存储 pair 的 vector
std::vector<std::pair<int, std::string>> vec = {{3, "c"}, {1, "a"}, {2, "b"}, {1, "a"}};
// 范围构造:从 vec 的迭代器区间创建 map
std::map<int, std::string> range_map(vec.begin(), vec.end());
// 遍历:自动去重+升序(1:a, 2:b, 3:c)
for (const auto& p : range_map) {
std::cout << p.first << ":" << p.second << " "; // 输出:1:a 2:b 3:c
}
return 0;
}
5. 初始化列表构造(最常用,C++11)
接收 initializer_list<pair<const Key, T>>,本质是范围构造的语法糖,直接传入键值对列表,底层自动转换为 pair 并插入。
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
// 简化写法(推荐):隐式构造 pair
std::map<int, std::string> init_map1 = {{1, "a"}, {2, "b"}, {3, "c"}};
// 等价于显式 pair 写法
std::map<int, std::string> init_map2 = {std::make_pair(1, "a"), std::make_pair(2, "b")};
//也可以传pair
std::pair<std::string,std::string> kv1("sort", "排序");
std::pair<std::string,std::string> kv2("left", "左边");
std::pair<std::string,std::string> kv3("right", "右边");
std::pair<std::string,std::string> kv4("insert", "插入");
std::map<string, string> dict{kv1,kv2,kv3};
//匿名对象
std::map<std::string,std::string> dict2 {std::pair<std::string,std::string>("sort", "排序"),std::pair<std::string,std::string>("left", "左边")};
for (const auto& p : init_map)
{
std::cout << p.first << ":" << p.second << " "; // 输出:1:a 2:b 3:c
}
//C++17 结构化绑定
for (auto&[k, v] : init_map)
{
std::cout << k<< ":" << v<< endl;
}
//// 输出:1:a 2:b 3:c
return 0;
}
1.3 map的迭代器和常规接口
这些太常规了 就不讲了 需要可以自行查阅 点击连接转跳
这些接口可以返回 比较键 详情可以参考 set章节
对于
find、count、lower_bound、upper_bound等接口 其实跟set都大差不差 也可以参考 set章节对于
value_comp接口 后面会写。
1.4 修改类接口(map的增删查等)

你提供的是std::map的修改类接口(Modifiers),这些接口用于对map进行插入、删除、交换等操作,以下是每个接口的作用、用法和核心特点:
1. insert:插入元素
-
作用 :向map中插入键值对(
pair<Key, T>),若键已存在则不插入(保证键的唯一性)。 -
常用用法 :
cpp// 方式1:插入pair临时对象 map.insert(pair<int, string>(1, "a")); // 方式2:插入make_pair生成的pair map.insert(make_pair(2, "b")); // 方式3:列表初始化(C++11+) map.insert({{3, "c"}, {4, "d"}}); -
返回值 :
pair<iterator, bool>------iterator:指向插入的元素(或已存在的同键元素);bool:true表示插入成功,false表示键已存在。
补充:其实
2. erase:删除元素
-
作用:删除map中的元素,支持按"键""迭代器""范围"删除。
-
常用用法 :
cpp// 方式1:按键删除(返回删除的元素个数,map中最多为1) size_t cnt = map.erase(1); // 方式2:按迭代器删除(需保证迭代器有效) auto it = map.find(2);//find是通过key去找的 if (it != map.end()) map.erase(it); // 方式3:按范围删除(删除[first, last)区间的元素) map.erase(map.begin(), map.find(3));
3. swap:交换内容
-
作用:交换两个map的所有元素(底层红黑树资源直接交换,效率高)。
-
用法 :
cppmap<int, string> m1{{1, "a"}}, m2{{2, "b"}}; m1.swap(m2); // 交换后m1是{2:"b"}, m2是{1:"a"} -
注意 :交换的两个map需是同类型(Key、T、Compare、Allocator完全一致)。
4. clear:清空内容
-
作用:删除map中所有元素,使其变为空map(底层红黑树节点被销毁)。
-
用法 :
cppmap.clear(); // 清空后map.empty()返回true
5. emplace(C++11+):构造并插入元素
-
作用 :直接在map内部构造键值对(避免临时pair的拷贝/移动,比
insert更高效)。 -
用法 :直接传入构造
pair所需的参数(无需显式创建pair):cpp// 等价于insert(pair<int, string>(1, "a")),但更高效 map.emplace(1, "a");
6. emplace_hint(C++11+):带位置提示的构造插入
-
作用:在指定迭代器位置的"附近"构造并插入元素,若提示位置准确,可减少红黑树的查找次数,提升效率。
-
用法 :第一个参数是"提示迭代器",后续参数是构造pair的参数:
cpp// 提示在begin()附近插入,若键1确实在begin()附近,插入效率更高 map.emplace_hint(map.begin(), 1, "a"); -
注意:提示位置仅作优化,即使提示错误,元素仍会被插入到正确位置(按键排序)。
1.5 equal_range

1. 核心作用
由于 map 的键是唯一 的,equal_range 会返回一个"范围"(实际是包含两个迭代器的 pair):
- 第一个迭代器(
first):指向等于目标键的元素(若存在); - 第二个迭代器(
second):指向大于目标键的第一个元素;
若目标键不存在,则 first 和 second 都指向该键应该插入的位置 (等价于 lower_bound(key) 的返回值)。
返回值是 pair<iterator, iterator>,其中:
pair.first=lower_bound(key)(第一个不小于key的元素);pair.second=upper_bound(key)(第一个大于key的元素);
2. 用法示例
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<int, std::string> m = {
{1, "a"}, {3, "c"}, {5, "e"}
};
// 1. 查找存在的键
auto range1 = m.equal_range(3);
if (range1.first != range1.second) { // 键存在
std::cout << "找到键3:" << range1.first->first << ":" << range1.first->second << std::endl;
}
// 2. 查找不存在的键
auto range2 = m.equal_range(2);
if (range2.first == range2.second) { // 键不存在
std::cout << "键2不存在,应插入位置:" << range2.first->first << "(之前)" << std::endl;
}
return 0;
}
输出:
找到键3:3:c
键2不存在,应插入位置:3(之前)
3. 与 find/lower_bound/upper_bound的关系
在 map 中(键唯一),equal_range 等价于:
cpp
auto range = make_pair(m.lower_bound(key), m.upper_bound(key));
且:
- 若
range.first != range.second→ 键存在,range.first就是find(key)的返回值; - 若
range.first == range.second→ 键不存在,range.first是键的插入位置。
| 接口 | 核心作用 | 返回值 | 键存在时行为 | 键不存在时行为 | 适用场景 |
|---|---|---|---|---|---|
find(key) |
查找是否存在目标键 | 迭代器(指向匹配元素 / end()) |
返回指向该键的迭代器 | 返回 end() |
仅判断键是否存在/获取对应值 |
lower_bound(key) |
找第一个不小于 key 的元素 |
迭代器(指向首个≥key的元素 / end()) |
返回指向该键的迭代器 | 返回键应插入的位置(首个>key的元素) | 找键的插入位置/判断键是否"不小于" |
upper_bound(key) |
找第一个大于 key 的元素 |
迭代器(指向首个>key的元素 / end()) |
返回首个>key的元素迭代器 | 返回键应插入的位置(首个>key的元素) | 找目标键的"下一个"元素 |
equal_range(key) |
一次性获取 lower_bound 和 upper_bound |
pair<迭代器, 迭代器>([first, second)) |
first=该键,second=首个>key | first=second=插入位置 | 同时需要"键位置+插入位置"/适配multimap |
1.6 map的修改获取数据
1.operator[]

- 这是
std::map类的一个重载操作符,即operator[]。 - 它允许通过键(key)来访问或插入元素,类似于数组的下标访问方式。
2.例子
cpp
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> myMap;
// 插入并获取值
myMap["apple"] = 5; // 插入 "apple" -> 5
// 修改已存在的值
myMap["apple"] = 10; // 修改为 10
// 访问不存在的 key,会自动插入默认值(0)
std::cout << myMap["banana"] << std::endl; // 输出: 0
return 0;
}
3. 原理

cpp
(*((this->insert(make_pair(k, mapped_type())).first)).second
这是
std::map::operator[]的等价行为描述 ,意思是:"对这个函数的调用,等价于执行下面这行表达式。"
我们从最外层开始,一层一层地拆解:
make_pair(k, mapped_type())
- 创建一个
std::pair<const key_type, mapped_type>对象。k是传入的键(key)。mapped_type()是值类型的默认构造函数调用(例如:如果mapped_type是int,则为0;如果是std::string,则为空字符串"")。- 所以这是创建一个键值对:
(k, 默认值)。
💡 示例:如果
map<int, std::string>,那么make_pair(5, mapped_type())就是(5, "")
this->insert(...)
- 调用当前
map对象的insert成员函数,插入上面创建的键值对。insert返回一个std::pair<iterator, bool>:.first是指向插入位置的迭代器(或已存在的元素)。.second是布尔值,表示是否成功插入了新元素(true表示插入,false表示已有该键)。
📌 注意:即使键已经存在,insert不会修改原值,只会返回现有元素的迭代器。
.first
- 取得
insert返回的pair中的第一个元素 ------ 即一个iterator,指向 map 中对应键的元素。
*(...)
- 对迭代器进行解引用(dereference),得到一个
pair<const key_type, mapped_type>&引用。- 即得到了键值对本身。
.second
- 获取这个键值对中的"值"部分(即
mapped_type类型的数据)。- 返回的是一个 引用,所以可以用来读取或修改值。
总结:
先尝试将键
k和一个默认构造的值插入到 map 中;然后获取插入/查找结果的迭代器;
解引用它,再取其
.second(即值部分);最终返回这个值的引用。
4. 实际效果
这就是 std::map::operator[](const key_type& k) 的语义等价形式:
cpp
mapped_type& operator[](const key_type& k) {
return (*this->insert(make_pair(k, mapped_type())).first).second;
}
- 如果键
k已存在 :- 不插入新元素。
- 返回已有元素的值的引用。
- 如果键
k不存在 :- 插入一个新的键值对
(k, 默认值)。 - 返回新插入元素的值的引用(可写)。
- 插入一个新的键值对
⚠️:at本质和operator[]本质基本没区别。
2. multimap
std::multimap 是 C++ 标准模板库(STL)中的一种关联容器,与 std::map 类似,但它允许同一个键(key)对应多个值(value)。这是它与 std::map 最核心的区别
2.1 基本特性
| 特性 | 说明 |
|---|---|
| 键是否唯一 | ❌ 不唯一(允许多个相同键) |
| 有序性 | ✅ 元素按键自动排序(默认升序) |
| 底层实现 | 通常为红黑树(Red-Black Tree) |
| 时间复杂度 | 插入、删除、查找:O(log n) |
支持 operator[] |
❌ 不支持(原因见下文) |
| 头文件 | #include <map> |
2.1 常用成员函数
1. 插入元素
cpp
std::multimap<std::string, int> mm;
mm.insert({"apple", 1});
mm.insert(std::make_pair("apple", 2));
mm.emplace("banana", 3);
所有插入操作都会成功,即使键已存在。
2. 查找元素(关键!)
find(key)
- 返回第一个匹配键的迭代器。
- 如果未找到,返回
end()。
cpp
auto it = mm.find("apple");
if (it != mm.end()) {
std::cout << it->second; // 输出第一个 "apple" 的值
}
equal_range(key)
- 返回一个
pair<iterator, iterator>,表示所有匹配键的范围。- 这是遍历所有同键元素的标准方式。
cpp
auto [first, last] = mm.equal_range("apple"); // C++17 结构化绑定
for (auto it = first; it != last; ++it) {
std::cout << it->second << " ";
}
// 输出: 1 2 (假设插入了两个 "apple")
✅
count(key)
- 返回指定键的出现次数。
cpp
size_t cnt = mm.count("apple"); // 返回 2
2.2 为什么没有 operator[]?
std::multimap 故意不提供 operator[],原因如下:
- 语义模糊
如果mm["apple"]存在,它应该返回哪个值?第一个?最后一个?还是所有值?无法确定。 - 自动插入行为冲突
map::operator[]会在键不存在时插入默认值。但在multimap中,插入"一个默认值"会破坏"多值"的设计意图,且用户无法控制插入数量。 - 接口一致性
提供operator[]会误导用户以为它像map一样是"单值映射",不符合multimap的本质。
💡 结论 :使用
find()或equal_range()是访问multimap元素的正确方式。
2.3 完整示例
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::multimap<std::string, int> scores;
// 插入多个相同键
scores.insert({"Alice", 85});
scores.insert({"Bob", 90});
scores.insert({"Alice", 92}); // 同一个学生多次成绩
scores.insert({"Alice", 88});
// 遍历所有 "Alice" 的成绩
auto range = scores.equal_range("Alice");
std::cout << "Alice's scores:\n";
for (auto it = range.first; it != range.second; ++it) {
std::cout << " " << it->second << "\n";
}
// 输出:
// Alice's scores:
// 85
// 88
// 92
// 统计 Bob 的成绩数量
std::cout << "Bob has " << scores.count("Bob") << " score(s).\n";
return 0;
}
2.4 总结
| 容器 | 键唯一? | 支持 [] |
适用场景 |
|---|---|---|---|
std::map |
✅ 是 | ✅ 是 | 一对一映射,需要快速按 key 访问/修改 |
std::multimap |
❌ 否 | ❌ 否 | 一对多映射,需要按键分组查询 |
选择 multimap 当你需要一个键对应多个值 ,并接受使用 equal_range 等方法来访问数据。
3.例题
3.1 随机链表的复制
思路:利用map 让random和cur->random建立映射关系。
cpp
class Solution {
public:
// 函数:复制一个带有 random 指针的链表
// 输入:原链表的头节点 head(可能为 nullptr)
// 返回:新链表的头节点(深拷贝)
Node* copyRandomList(Node* head) {
// 1. 创建一个 map,用于建立「原节点 → 新节点」的映射关系
// 这样我们就能通过原节点的 random 指针快速找到对应的新节点
map<Node*, Node*> nodeMap;
// 2. 初始化新链表的头指针和尾指针(用于构建新链表)
Node* copyhead = nullptr; // 新链表的头节点
Node* copytail = nullptr; // 新链表的尾节点(用于高效尾插)
// 3. cur 指向原链表当前遍历的节点,从 head 开始
Node* cur = head;
// 4. 第一遍遍历:复制所有节点的 val 和 next 关系,并建立映射
while (cur) { // 只要 cur 不为空,就继续遍历
// 5. 如果 copytail 是空,说明新链表还没创建第一个节点
if (copytail == nullptr) {
// 6. 创建第一个新节点,值为 cur->val
// 同时让 copyhead 和 copytail 都指向它
copyhead = copytail = new Node(cur->val);
} else {
// 7. 否则,在 copytail 后面追加一个新节点
copytail->next = new Node(cur->val);
// 8. 移动 copytail 到新节点,保持它始终指向新链表尾部
copytail = copytail->next;
}
// 9. 将「原节点 cur」和「对应的新节点 copytail」存入映射表
// 这是关键!后续设置 random 指针全靠这个映射
nodeMap[cur] = copytail;
// 10. 移动 cur 到原链表的下一个节点
cur = cur->next;
}
// 11. 第二遍遍历:设置每个新节点的 random 指针
// 重置 cur 回到原链表头部
cur = head;
// 12. copy 指向新链表当前处理的节点,从 copyhead 开始
Node* copy = copyhead;
// 13. 再次遍历原链表(此时新链表结构已完整,只缺 random)
while (cur) {
// 14. 如果原节点的 random 是 nullptr,新节点的 random 也设为 nullptr
if (cur->random == nullptr) {
copy->random = nullptr;
} else {
// 15. 否则,通过映射表 nodeMap 找到原 random 指向节点
// 对应的新节点,并赋值给 copy->random
// 例如:cur->random 指向原链表的第3个节点,
// nodeMap[cur->random] 就是新链表中对应的第3个节点
copy->random = nodeMap[cur->random];
}
// 16. 同步移动两个指针:cur(原链表)和 copy(新链表)
cur = cur->next;
copy = copy->next;
}
// 17. 返回新链表的头节点,完成深拷贝
return copyhead;
}
};
为什么需要两遍遍历?
必须先创建所有新节点,否则 random 指针可能指向尚未创建的节点。
3.2 前K个高频单词
1.法一
cpp
class Solution {
public:
// 自定义比较函数对象(仿函数)
// 用于定义 pair<string, int> 的排序规则
struct Compare {
// 重载 () 运算符,使其可作为函数调用
bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) {
// 按照 频次(value)降序排列:频次高的排在前面
return kv1.second > kv2.second;
}
};
// 函数:返回出现频率最高的 k 个单词
// 要求:若频次相同,则按字典序升序排列(即 "apple" < "zoo")
vector<string> topKFrequent(vector<string>& words, int k) {
// 1. 使用 map 统计每个单词的出现次数
// 注意:map 默认按键(string)字典序升序排序
map<string, int> countMap;
for (auto& str : words) {
countMap[str]++; // 自动初始化为0后加1
}
// 此时 countMap 中的元素已按键(单词)字典序排好
// 2. 将 map 中的所有键值对拷贝到 vector 中,便于后续排序 因为sort要求随机迭代器
// vector 元素类型为 pair<string, int>
vector<pair<string, int>> v(countMap.begin(), countMap.end());
// 3. 使用 stable_sort 对 vector 进行稳定排序
// 排序规则由 Compare 定义:按频次降序
// 关键点:stable_sort 会保持"相等元素"的相对顺序
// 而由于 v 初始化自 map(已按字典序排好),
// 所以当两个单词频次相同时,它们在 v 中的原始顺序就是字典序!
stable_sort(v.begin(), v.end(), Compare());
// 4. 提取前 k 个单词(此时已满足:高频优先,同频则字典序小的在前)
vector<string> ret;
for (int i = 0; i < k; ++i) {
ret.push_back(v[i].first); // 只取单词(key)
}
return ret;
}
};
✅ 为什么使用
stable_sort而不是sort?
- Compare 只比较 频次(second),不涉及单词(first)。
- 当两个单词频次相等时,Compare 返回 false(因为 a.second > b.second 不成立),但标准 sort 不保证相等元素的顺序。
- 而 stable_sort 会保留原始相对顺序。
- 由于 v 是从 map 构造而来(已按字典序排好),所以:
- 频次相同时,字典序小的单词在 v 中位置靠前;
- stable_sort 保持这个顺序 → 最终结果满足"同频按字典序升序"。
当然 如果你偏要用sort(毕竟效率高一点)也可以 我们可以修改一下仿函数。
cpp
struct Compare
{
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);
}
};
2.法二
利用priority_queue
cpp
class Solution {
public:
// 自定义比较器:用于构建最小堆(但目标是保留"最大"的 k 个元素)
struct Compare {
// 注意:priority_queue 的比较器行为与 sort 相反!
// 若 comp(a, b) 返回 true,则 a 会被认为"优先级更低",排在堆底
bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) {
// 我们希望堆顶是"当前最不想要的元素",以便弹出
// 所以堆应按以下规则排序(小顶堆):
// 1. 频次低的优先级低(应留在堆底)→ 但我们希望频次高的保留
// → 所以当 kv1.freq < kv2.freq 时,kv1 应该在堆底 → 返回 true
// 2. 频次相同时,字典序大的优先级低(因为题目要求同频时字典序小的优先)
// → 所以当 kv1.word > kv2.word 时,kv1 应该在堆底 → 返回 true
return kv1.second < kv2.second ||
(kv1.second == kv2.second && kv1.first > kv2.first);
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
// 1. 使用 map 统计词频(map 会自动按键字典序升序排序)
map<string, int> countMap;
for (auto& str : words) {
countMap[str]++;
}
// 此时 countMap: { "a":2, "b":1, "c":2 } 等,已按字典序排好
// 2. 构造一个优先队列(堆),使用自定义 Compare
// 注意:这里直接用整个 countMap 初始化堆!
// 这会导致堆包含所有唯一单词(n 个),而不是只维护 k 个
priority_queue<pair<string, int>, vector<pair<string, int>>, Compare>
pq(countMap.begin(), countMap.end());
// 3. 从堆中取出前 k 个元素(堆顶是"当前最符合要求"的?)
vector<string> ret;
for (int i = 0; i < k; ++i) {
ret.push_back(pq.top().first); // 取单词
pq.pop(); // 弹出
}
return ret;
}
};







