引言
哈希表(Hash Table),也被称为散列表,是一种通过哈希函数(Hashing Function)组织数据,以支持快速插入和搜索的数据结构。它是基于键(Key)的映射存储机制,允许我们快速访问、插入和删除数据项。哈希表的基本思想是通过哈希函数将输入的键转换为数组的一个索引位置,从而能够快速定位到数据。
哈希表的主要组成部分
-
哈希函数:用于将键(Key)映射到数组的一个索引位置。理想的哈希函数能够将不同的键均匀地映射到数组的不同位置,以减少冲突(Collision)的发生。
-
数组:哈希表通常使用一个数组来存储数据项。数组的索引位置由哈希函数确定,但由于哈希函数的输出范围可能远大于数组的实际大小,因此会出现多个键映射到同一个索引位置的情况,即冲突。
-
冲突解决机制:当不同的键映射到同一个索引位置时,需要采用某种策略来解决冲突。常见的冲突解决策略有开放寻址法(Open Addressing)和链地址法(Chaining)。
- 开放寻址法:当冲突发生时,继续在哈希表的数组中查找下一个空闲位置,直到找到为止。
- 链地址法:在每个数组索引位置维护一个链表,所有映射到该索引的键都存储在链表中。
哈希表的性能
-
时间复杂度:理想情况下,哈希表的插入、删除和查找操作的时间复杂度都是O(1),即常数时间。但在最坏情况下(如所有键都映射到同一个索引位置),这些操作的时间复杂度可能会退化到O(n),其中n是哈希表中元素的数量。
-
空间复杂度:哈希表的空间复杂度主要由其数组的大小和冲突解决机制决定。链地址法由于需要额外的空间来存储链表,其空间复杂度可能会稍高。
哈希表的应用
哈希表由于其高效的查找、插入和删除操作,被广泛应用于各种编程和数据结构任务中,包括但不限于:
- 数据缓存:如Web缓存、数据库缓存等。
- 集合实现:如HashSet、HashMap等。
- 编译器实现:用于符号表的实现,以快速查找和插入变量名、函数名等。
- 网络安全:在加密算法和协议中使用哈希表来存储和验证数据。
经典例题
1. 两数之和
题目描述:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。
解题思路:使用哈希表来存储遍历过的元素及其索引,对于每个元素,检查 target - nums[i]
是否已经存在于哈希表中,如果存在,则返回这两个元素的索引。
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) {
if (hash.count(target - nums[i])) {
return {hash[target - nums[i]], i};
}
hash[nums[i]] = i;
}
return {};
}
};
2. 字母异位词分组
题目描述:给你一个字符串数组,请你将字母异位词组合在一起。可以按任意顺序返回结果列表。
解题思路:将每个字符串排序后的结果作为哈希表的键,然后将原字符串添加到对应键的列表中。
#include <unordered_map>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> hash;
for (const string& str : strs) {
string sortedStr = str;
sort(sortedStr.begin(), sortedStr.end());
hash[sortedStr].push_back(str);
}
vector<vector<string>> res;
for (const auto& entry : hash) {
res.push_back(entry.second);
}
return res;
}
};
3. 判断是否是字母异位词
题目描述:给定两个字符串 s 和 t,编写一个函数来判断 t 是否是 s 的字母异位词。
解题思路:使用一个长度为 26 的数组来记录每个字母在字符串中出现的次数,然后比较两个字符串的字母频率是否相同。
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
vector<int> record(26, 0);
for (char c : s) {
record[c - 'a']++;
}
for (char c : t) {
record[c - 'a']--;
}
for (int count : record) {
if (count != 0) {
return false;
}
}
return true;
}
};
4. 数组中的最长连续序列
题目描述:给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
解题思路:使用哈希表存储所有数字,然后遍历哈希表,对于每个数字,如果它减一不在哈希表中,则从它开始计算连续序列的长度。
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(), nums.end());
int longestStreak = 0;
for (int num : numSet) {
if (!numSet.count(num - 1)) {
int currentNum = num;
int currentStreak = 1;
while (numSet.count(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}