哈希表(Hash table)是一种通过键值对存储数据的数据结构,其核心优势在于平均 O (1) 时间复杂度的插入、删除和查找操作。本文将通过三道 LeetCode 经典题目,带大家掌握哈希表在 C++ 中的实战用法,所有代码均附带详细注解。
一、LeetCode 1. 两数之和(简单)
题目描述
给定整数数组nums和目标值target,返回两个数的下标,使它们的和等于target。
解题思路
利用哈希表存储「数值 - 下标」映射,遍历数组时通过差值快速查找互补元素。
cpp
class Solution {
public:
// 定义twoSum函数,参数为整数向量nums(待查找的数组)和目标值target
// 返回值为vector<int>,存储两个符合条件的元素下标
vector<int> twoSum(vector<int>& nums, int target) {
// 外层循环:遍历数组中的每个元素,作为第一个加数
// i为第一个元素的下标,从0开始
for (int i = 0; i < nums.size(); i++) {
// 内层循环:遍历当前元素之后的所有元素,作为第二个加数
// j从i+1开始,避免重复计算(如i=0,j=1与i=1,j=0是同一对)
for (int j = i + 1; j < nums.size(); j++) {
// 判断两个元素的和是否等于目标值
if (nums[i] + nums[j] == target) {
// 若符合条件,返回包含两个下标i和j的向量
return {i, j};
}
}
}
// 题目保证有解,此处返回空向量仅为语法兼容(实际不会执行到)
return {};
}
};
核心注解
unordered_map是 C++ 标准库中的哈希表实现,支持 O (1) 平均时间的查找。- 遍历数组时边查边存,避免了二次遍历,时间复杂度从暴力法的 O (n²) 优化为 O (n)。
- 通过
find()方法判断元素是否存在,比count()更高效(找到即停止查找)。
二、LeetCode 49. 字母异位词分组(中等)
题目描述
将所有字母异位词(字母相同但顺序不同的单词)分组,返回分组后的列表。
解题思路
字母异位词排序后结果相同,以此作为哈希表的键,实现分组。
cpp
class Solution {
public:
// 函数功能:将输入的字符串数组按字母异位词分组,返回分组后的二维字符串数组
// 字母异位词定义:由相同字母按不同顺序组成的字符串(如"eat"和"tea")
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// 定义哈希表:key为"排序后的字符串"(作为字母异位词的唯一标识)
// value为vector<string>,存储该组所有字母异位词
unordered_map<string, vector<string>> map;
// 遍历输入的每一个字符串
for (string str : strs) {
// 复制当前字符串并排序,得到其"特征key"
// 例如"eat"排序后为"aet","tea"排序后也为"aet",因此共享同一key
string key = str;
sort(key.begin(), key.end()); // 对字符串按字符ASCII码排序
// 将原字符串加入哈希表中对应key的分组
// 若key不存在,map会自动创建一个新的vector并插入该字符串
map[key].push_back(str);
}
// 收集哈希表中所有的分组,整理为结果返回
vector<vector<string>> result;
// 遍历哈希表的每一对键值对(pair.first为key,pair.second为分组)
for (auto& pair : map) {
result.push_back(pair.second); // 将分组加入结果数组
}
return result;
}
};
核心注解
- 排序是关键:通过
sort()将字母异位词转换为相同的key,例如 "eat" 和 "tea" 排序后都是 "aet"。 groupMap[key].push_back(str)利用了unordered_map的特性:访问不存在的key时会自动插入默认值(空向量)。- 时间复杂度为 O (n・k log k),其中 n 是字符串数量,k 是字符串最大长度(排序耗时 O (k log k))。
三、LeetCode 128. 最长连续序列(中等)
题目描述
给定未排序整数数组,找出最长连续序列的长度,要求时间复杂度 O (n)。
解题思路
用哈希集合去重并快速查询,仅从序列起点开始遍历,避免重复计算。
cpp
class Solution {
public:
// 函数功能:找出未排序整数数组中最长连续序列的长度
// 例如:输入[100,4,200,1,3,2],最长连续序列为[1,2,3,4],返回4
int longestConsecutive(vector<int>& nums) {
// 1. 将数组元素存入哈希集合,实现去重和O(1)时间的存在性判断
// nums.begin()和nums.end()是迭代器,用于初始化集合
unordered_set<int> numSet(nums.begin(), nums.end());
int longest = 0; // 记录最长连续序列的长度,初始为0
// 2. 遍历集合中的每个元素,寻找连续序列的起点
for (int num : numSet) {
// 关键判断:只有当num-1不存在时,num才是某个连续序列的起点
// 避免对序列中间元素重复计算(例如序列1-2-3,只从1开始计算)
if (numSet.find(num - 1) == numSet.end()) {
int currentNum = num; // 当前序列的起点
int currentLength = 1; // 当前序列的长度(至少包含起点)
// 3. 从起点开始,向后查找连续的数字
// 若currentNum + 1存在于集合中,则继续延长序列
while (numSet.find(currentNum + 1) != numSet.end()) {
currentNum++; // 移动到下一个连续数字
currentLength++; // 序列长度加1
}
// 4. 更新最长序列长度
longest = max(longest, currentLength);
}
}
return longest; // 返回最长连续序列的长度
}
};
核心注解
unordered_set用于去重(避免重复元素影响结果)和快速判断元素是否存在。- 起点判断
numSet.find(num - 1) == numSet.end()是优化核心,确保每个元素只被遍历一次。 - 时间复杂度 O (n):每个元素要么作为起点被遍历一次,要么被后续元素检查一次(总计 O (n))。