用 std::unordered_map 统计频次再遍历找最大值是求众数的标准解法,平均 o(1) 时间优于 std::map;注意键类型需支持哈希与等号、空输入处理、线程不安全及小范围数据可用数组优化。

用 std::unordered_map 统计频次是最直接的解法
众数本质是出现次数最多的元素,C++里没有内置“求众数”函数,必须自己统计频次再找最大值。不用手写哈希表,std::unordered_map 就是标准答案——它平均 O(1) 插入和查询,比 std::map(红黑树,O(log n))更合适。
常见错误是把 std::map 当默认选择,尤其在刷题时没注意性能差异,大数据量下容易超时。
- 键类型必须支持
operator==和哈希函数,基础类型(int、char、std::string)都自带 - 对自定义结构体,得手动提供哈希特化(
std::hash<t></t>)和等号重载,否则编译报错:error: call to implicitly-deleted default constructor of 'std::hash<mystruct>'</mystruct> - 插入时别用
map[key]++直接累加,它会先默认构造 value(对 int 是 0),没问题;但对复杂类型可能触发不必要的构造
遍历一次数组 + 一次 map 就能拿到众数
不需要排序,也不需要两次遍历原数组。统计完频次后,再遍历 unordered_map 找最大 value 对应的 key 即可。注意:众数不唯一,题目若只要一个,取第一个最大值就行;若要全部,得额外存所有 max_count 的 key。
容易踩的坑是边插边更新最大值——看似省一次遍历,但逻辑易错,比如初始 max_count = 0,而数组全为负数(不影响 int 频次,但思维惯性会误判),或空输入没处理。
立即学习“C++免费学习笔记(深入)”;
- 空容器要提前返回,否则遍历空 map 行为未定义
- 用
int max_count = -1初始化,比0更安全(虽然频次不会负,但防御性习惯) - 示例片段:
std::unordered_map<int, int> freq;<br>for (int x : nums) freq[x]++;<br>int mode = nums[0], max_count = 0;<br>for (const auto& p : freq) {<br> if (p.second > max_count) {<br> max_count = p.second;<br> mode = p.first;<br> }<br>}
当数据范围小且固定时,数组代替哈希表更快
如果题目明确说数值在 [0, 1000] 或 [-10⁴, 10⁴] 这种有限区间,用 vector 或普通数组做计数桶,比 unordered_map 更快也更省内存——无哈希计算、无动态扩容、缓存友好。
典型错误是开数组大小写错:比如值域是 [-5000, 5000],长度得是 10001,还要偏移 5000;漏掉偏移就会越界访问,引发未定义行为(vector::at 会抛异常,[] 不检查)。
- 偏移量 = -min_value,数组长度 = max_value - min_value + 1
- 访问时下标 = 原值 + 偏移量,务必检查是否在 [0, size) 内
- 对 unsigned 类型或非负数,偏移可省,直接当索引用
多线程或高频调用场景下注意 unordered_map 的线程安全性
std::unordered_map 本身不是线程安全的:多个线程同时写(哪怕写不同 key)会导致崩溃;读写并发也不行。只读可以多线程,但前提是 map 构建完成后不再修改。
实际项目中容易忽略这点,比如在初始化阶段用单线程构建好频次表,之后交给多个 worker 线程只读查询——这没问题;但如果一边统计一边被其他线程查,就必须加锁(std::shared_mutex 比 std::mutex 更适合读多写少)。
- STL 容器的线程安全规则统一:多读 OK,读写/多写必须同步
- 不要依赖“我只改不同 bucket”这种底层假设,标准不保证
- 若需并发安全哈希表,考虑第三方库如
tbb::concurrent_hash_map,而非自己加锁封装
事情说清了就结束。真正卡住人的往往不是算法逻辑,而是哈希键类型不支持、空输入没判断、线程场景下误信“只读安全”这些细节。









