备战蓝桥杯-哈希

本文是基于C++语言,在备战蓝桥杯算法竞赛过程中,通过对力扣Hot100哈希表相关题型的刷题和总结,归纳出的哈希表核心知识点、常用技巧和实战经验。当然后面也会持续更新一些具体题的解析,感兴趣的朋友可以关注一下哦。

一、哈希表容器对比与选择

1.1哈希相关容器对比

特性维度 unordered_map map unordered_set set
底层实现 哈希表 红黑树 哈希表 红黑树
元素有序性 无序 有序 无序 有序
查找时间复杂度 O(1) 平均 O(log n) O(1) 平均 O(log n)
内存占用 较高(哈希桶) 较低 较高(哈希桶) 较低
常用场景 键值对快速查找 有序键值对 快速存在性判断 有序集合

竞赛中大部分的情况使用unordered_mapunordered_set,仅当确实需要有序性时才使用mapset

1.2哈希表四大应用场景

①频率统计

通过建立元素到出现次数的映射,我们可以在一次遍历中完成所有元素的计数工作。这种方法的优势在于:

  • 时间复杂度O(n):只需一次遍历

  • 代码简洁 :利用operator[]的自动初始化特性

  • 灵活性强:可轻松扩展为带条件的统计

cpp

复制代码
unordered_map<int, int> countFrequency(const vector<int>& nums) {
    unordered_map<int, int> freq;
    for (int num : nums) {
        freq[num]++; 
    }
    return freq;
}

注意事项

  1. 默认值初始化freq[key]++会为不存在的键创建默认值0,然后加1

  2. 内存考虑:当元素种类很多但每个出现次数很少时,哈希表可能占用较多内存

②去重判断

利用集合的唯一性特性,快速判断元素是否已经存在。这种场景通常比排序去重更高效:

  • 实时性:可以在遍历过程中实时判断

  • 灵活性:可与其他操作结合(如遍历图时的访问标记)

  • 简洁性:代码比排序去重更简单

cpp

复制代码
// 判断数组中是否有重复元素
bool hasDuplicate(const vector<int>& nums) {
    unordered_set<int> seen;
    
    for (int num : nums) {
        // 如果已经存在,说明有重复
        if (seen.count(num) > 0) {
            return true;
        }
        seen.insert(num);
    }
    return false;
}

③索引映射

建立两个集合之间的双向查找关系,或者缓存中间计算结果以便复用:

  • 双向查找:通过键快速找到值,反之也可通过值找到键(需要两个映射)

  • 缓存加速:存储已计算结果,避免重复计算

  • 关系维护:维护对象之间的关联关系

cpp

复制代码
// 建立单词到ID的映射
unordered_map<string, int> buildWordIndex(const vector<string>& words) {
    unordered_map<string, int> wordToId;
    int nextId = 0;
    
    for (const string& word : words) {
        // 如果单词还没有ID,分配一个新ID
        if (wordToId.find(word) == wordToId.end()) {
            wordToId[word] = nextId++;
        }
    }
    
    return wordToId;
}

④有序遍历

当需要按键的顺序处理元素时,使用基于红黑树的mapset

  • 自动排序:元素总是按键排序(默认升序)

  • 范围查询 :支持lower_boundupper_bound等范围操作

  • 顺序遍历:遍历时元素按顺序出现

cpp

复制代码
// 按键排序的频率统计
map<string, int> sortedWordCount(const vector<string>& words) {
    map<string, int> wordCount;
    
    for (const string& word : words) {
        wordCount[word]++;  // map会按键(字符串)自动排序
    }
    
    return wordCount;
}

注意事项

  1. 性能考量map的O(log n)虽然稳定,但比unordered_map的O(1)平均要慢

  2. 自定义排序:可以通过提供比较函数自定义排序规则

  3. 内存使用:红黑树节点比哈希表节点通常更节省内存

二、核心API使用详解

操作类型 函数/操作符 功能 返回值 使用场景
插入 insert({k,v}) 插入键值对 pair<迭代器, bool> 需要知道插入是否成功
插入/更新 operator[] 插入或更新值 值引用 频率统计、默认值插入
高效插入 emplace(k,v) 直接构造元素 pair<迭代器, bool> 插入复杂对象
查找 find(key) 查找元素 迭代器 标准查找操作
存在判断 count(key) 统计键数量 0或1 只需知道是否存在
删除 erase(key) 删除元素 删除的数量 按键删除
删除 erase(iter) 删除迭代器元素 下一迭代器 按位置删除
清空 clear() 清空容器 void 重置状态
大小 size() 元素数量 size_t 获取规模
判空 empty() 是否为空 bool 边界检查

2.1插入操作

①insert({k, v}) - 插入键值对

cpp

复制代码
unordered_map<string, int> scores;
auto result = scores.insert({"Alice", 95});  // 返回pair<iterator, bool>
if (result.second) {
    cout << "插入成功,键值对已添加" << endl;  // 当键不存在时插入成功
} else {
    cout << "插入失败,键已存在" << endl;      // 当键已存在时插入失败
}
// 典型场景:当需要明确知道插入是否成功,避免覆盖已有数据时使用

②operator[] - 插入或更新值

cpp

复制代码
unordered_map<string, int> wordCount;
wordCount["apple"] = 1;     // 插入新键值对:apple -> 1
wordCount["apple"] = 3;     // 更新已有键的值:apple -> 3
int count = wordCount["banana"];  // 键不存在时自动插入:banana -> 0
// 典型场景:频率统计、提供默认值,代码最简洁直观

③emplace(k, v) - 直接构造元素

cpp

复制代码
unordered_map<int, Person> people;
// 直接构造Person对象,避免创建临时对象再拷贝
auto result = people.emplace(101, Person("Bob", 25));
// result.first是迭代器,result.second表示是否插入成功
// 典型场景:插入复杂对象时性能更优,减少拷贝开销

2.2查找与访问操作

①find(key) - 查找元素

cpp

复制代码
unordered_map<int, string> idToName = {{1, "Alice"}, {2, "Bob"}};
auto it = idToName.find(2);           // 查找键为2的元素
if (it != idToName.end()) {
    cout << "找到:" << it->second;   // it->first是键,it->second是值
}
// 典型场景:标准查找操作,需要访问找到的元素时使用

②count(key) - 判断键是否存在

cpp

复制代码
unordered_set<int> visited;
visited.insert({1, 2, 3});
if (visited.count(2) > 0) {  // 返回0或1
    cout << "元素2已访问过" << endl;
}
// 典型场景:只需要知道元素是否存在,不需要访问具体值时使用

2.3删除操作

①erase(key) - 按键删除

cpp

复制代码
unordered_map<string, int> data = {{"A", 1}, {"B", 2}, {"C", 3}};
size_t erased = data.erase("B");  // 返回删除的元素数量(0或1)
cout << "删除了 " << erased << " 个元素" << endl;
// 典型场景:知道要删除的键时使用,简洁明了

②erase(iter) - 按迭代器位置删除

cpp

复制代码
unordered_map<int, string> map = {{1, "a"}, {2, "b"}, {3, "c"}};
auto it = map.find(2);
if (it != map.end()) {
    auto nextIt = map.erase(it);  // 返回被删除元素的下一个迭代器
    cout << "下一个键是:" << nextIt->first << endl;
}
// 典型场景:遍历过程中删除特定元素时使用

③erase(begin, end) - 删除范围

cpp

复制代码
unordered_map<int, int> scores;
for (int i = 1; i <= 10; i++) scores[i] = i * 10;

auto start = scores.find(3);
auto end = scores.find(8);
if (start != scores.end() && end != scores.end()) {
    scores.erase(start, end);  // 删除键在[3, 8)范围内的所有元素
}
// 典型场景:批量删除指定范围内的元素

2.4容量与状态操作

①size() - 获取元素数量

cpp

复制代码
unordered_set<int> numbers = {1, 2, 3, 4, 5};
cout << "集合中有 " << numbers.size() << " 个元素" << endl;
// 典型场景:需要知道容器中元素数量时使用

②empty() - 判断是否为空

cpp

复制代码
unordered_map<string, int> cache;
if (cache.empty()) {
    cout << "缓存为空,需要预热数据" << endl;
    cache["default"] = 100;
}
// 典型场景:初始化检查、边界条件判断

③clear() - 清空容器

cpp

复制代码
unordered_map<int, string> tempData;
// ... 填充数据 ...
tempData.clear();  // 清空所有元素,size()变为0
cout << "数据已清空,当前大小:" << tempData.size() << endl;
// 典型场景:重置状态、释放内存(但保留容量)

2.5遍历相关操作

begin() / end() - 获取迭代器

cpp

复制代码
unordered_map<string, int> grades = {{"Math", 90}, {"English", 85}};
for (auto it = grades.begin(); it != grades.end(); ++it) {
    cout << it->first << ": " << it->second << endl;
}
//或者
for (const auto& [key, value] : freq) { 
    cout << key << " appears " << value << " times" << endl; 
}
// 典型场景:遍历容器所有元素

2.6使用注意事项总结

operator[] vs find()

  • operator[]会创建不存在的键
  • 纯查找时用find()避免副作用

插入性能

  • 简单类型用operator[]insert
  • 复杂类型用emplace减少拷贝

删除安全

  • 删除后迭代器失效,不要再使用

  • 范围删除要确保迭代器有效

内存管理

  • clear()释放所有元素,但实现通常保留哈希桶结构, 具体是否释放内存由实现决定,不保证保留容量。
相关推荐
Reese_Cool1 小时前
【STL】蓝桥杯/天梯赛终极杀器!10个C++字符串核心技巧,暴力破解高频考点
开发语言·c++·蓝桥杯·stl
拼好饭和她皆失1 小时前
基础算法--写给算法小白的模板指南:快速掌握核心代码,蓝桥杯必备模板
算法
吞下星星的少年·-·1 小时前
rotate函数应用模板
算法
我想我不够好。1 小时前
监控学习 4.28 1.5 hour
学习
Stella Blog1 小时前
狂神Java基础学习笔记Day05
java·笔记·学习
AI科技星1 小时前
人类首张【全域数学公理体系】黑洞内部结构图—基于「0-1-∞」三元本源的全维深度解析
人工智能·算法·机器学习·数学建模·数据挖掘·量子计算
Alice-YUE1 小时前
前端性能优化完全指南:从指标到实战
前端·学习·性能优化
paeamecium1 小时前
【PAT甲级真题】- Recover the Smallest Number (30)
数据结构·算法·pat考试·pat
hehelm1 小时前
C++ 模拟实现 AVL 树
开发语言·c++