作为一名初学者,掌握 unordered_map 的用法是刷算法题的关键一步,它能解决非常多的问题。总结 unordered_map 的核心用法,并清晰地解释 set 和 map 的区别以及它们各自的使用场景。
一、 unordered_map 哈希表的核心用法 (算法题利器)
你可以把 unordered_map 想象成一个**“超级字典”**。普通字典是“单词 -> 释义”,而 unordered_map 是“键 (Key) -> 值 (Value)”。
它的最大特点是:查找、插入、删除都非常快,平均时间复杂度为 O(1),也就是几乎不花时间。
核心操作
#include <iostream>
#include <string>
#include <unordered_map>
// 声明一个 `unordered_map`,键是字符串类型,值是整数类型
std::unordered_map<std::string, int> my_map;
// 1. 插入/更新键值对 (像查字典一样,没有就新增,有就更新)
my_map["apple"] = 10; // 插入 "apple" -> 10
my_map["banana"] = 5;
my_map["apple"] = 12; // 更新 "apple" -> 12
// 2. 访问值
std::cout << my_map["apple"]; // 输出 12
// 3. 检查一个键是否存在 (非常重要!)
if (my_map.count("banana")) { // .count(key) 返回 1 (存在) 或 0 (不存在)
std::cout << "banana exists!";
}
// 4. 删除一个键值对
my_map.erase("banana");
// 5. 遍历哈希表 (C++17 语法,非常方便)
for (auto const& [key, val] : my_map) {
std::cout << key << ": " << val << std::endl;
}
在算法题中的三大经典应用场景
场景 1:频率计数 (Counting Frequencies)
问题类型:统计数组/字符串中每个元素出现的次数、寻找出现次数最多的元素、判断两个字符串是否为字母异位词等。
这是 unordered_map 最最常见的用法。
- Key: 数组中的元素或字符串中的字符。
- Value: 该元素出现的次数。
示例:统计字符串中每个字符的出现次数
#include <string>
#include <unordered_map>
std::string s = "hello world";
std::unordered_map<char, int> freq_counter;
for (char c : s) {
freq_counter[c]++; // 如果 c 不存在,会自动创建 c->0,然后++变为1;如果存在,直接++
}
// freq_counter['l'] 的值会是 3
场景 2:快速查找/建立“备忘录” (Fast Lookup / Seen Tracker)
问题类型:两数之和 (Two Sum)、无重复字符的最长子串、判断数组中是否有重复元素等。
你需要快速知道“某个元素是否出现过”以及它的“相关信息”(比如下标)。
- Key: 数组中的元素。
- Value: 该元素的下标或其他需要记录的信息。
示例:两数之和 (LeetCode 第1题)
在数组
nums中找两个数,使它们的和等于target,返回它们的下标。
#include <vector>
#include <unordered_map>
vector<int> twoSum(vector<int>& nums, int target) {
// Key: 数字, Value: 该数字的下标
std::unordered_map<int, int> seen_map;
for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i]; // 计算需要配对的另一个数
if (seen_map.count(complement)) { // 检查配对数是否在“备忘录”里
return {seen_map[complement], i}; // 找到了!
}
seen_map[nums[i]] = i; // 没找到,就把当前数和它的下标存入“备忘录”
}
return {};
}
场景 3:缓存计算结果 (Caching / Memoization)
问题类型:斐波那契数列、爬楼梯等动态规划或递归问题,其中有很多重复计算。
用来存储已经计算过的子问题的解,避免重复劳动。
- Key: 子问题的输入参数。
- Value: 该子问题的计算结果。
二、 set 和 map 的区别
为了方便理解,我把它们分成两组:set 家族和 map 家族。
核心区别
| 特性 | set 家族 (unordered_set, set) | map 家族 (unordered_map, map) |
|---|---|---|
| 存储内容 | 只存储单个元素 (Key) | 存储键值对 (Key-Value Pair) |
| 核心目的 | 判断一个元素是否存在,并保证所有元素唯一。 | 根据一个 Key,存取一个与之关联的 Value。 |
| 生活中的比喻 | 派对的宾客名单 | 电话本 |
| 解决的问题 | “张三在名单上吗?” | “张三的电话号码是多少?” |
简单来说:
- 当你只关心“一个东西在不在这里”,用
set。 - 当你关心“一个东西对应的信息是什么”,用
map。
unordered_ vs. std:: (哈希 vs. 有序)
这是一个性能和功能上的取舍。
| 类型 | std::unordered_set<br>std::unordered_map | std::set<br>std::map |
|---|---|---|
| 内部实现 | 哈希表 | 红黑树 (一种自平衡二叉搜索树) |
| 元素顺序 | 无序的 (遍历时顺序不确定) | 有序的 (元素总是按键排序) |
| 性能 | 查找、插入、删除平均 O(1) | 查找、插入、删除 O(log N) |
| 选择时机 | 绝大多数算法题的首选,因为追求极致的速度。 | 当你需要自动保持元素有序时才使用。 |
三、何时应该用?(给初学者的决策指南)
当你遇到一个问题时,可以这样问自己:
- 我需要存储“键值对”信息吗?(比如
数字->下标,字符->次数)- 是 -> 进入
map家族。- 我需要让这些键自动排序吗?
- 否 (99%的算法题) ->
std::unordered_map(你的首选!) - 是 ->
std::map
- 否 (99%的算法题) ->
- 我需要让这些键自动排序吗?
- 否,我只需要知道一个元素存不存在,或者需要一个不含重复元素的集合。 -> 进入
set家族。- 我需要让这些元素自动排序吗?
- 否 (绝大多数情况) ->
std::unordered_set(你的首选!) - 是 ->
std::set
- 否 (绝大多数情况) ->
- 我需要让这些元素自动排序吗?
- 是 -> 进入
总结:给初学者的黄金法则
在刷算法题时,如果你需要一个哈希结构,无脑先用
unordered_map或unordered_set。因为算法题通常最看重时间效率,O(1) 的性能优势是巨大的。只有当题目有明确要求(比如“按字母顺序输出所有独特的单词”)时,你才需要考虑使用有序的map或set。

960

被折叠的 条评论
为什么被折叠?



