总言
主要内容:编程题举例,熟悉理解如何运用哈希表。
文章目录
1、哈希表
什么时候需要用到哈希表? (这里仅作举例)
1、统计频率: 哈希表可以快速统计一个数组中各个元素出现的频率。例如,给定一个整型数组,你可以使用哈希表来记录每个元素出现的次数。
2、快速查找:哈希表的主要优势在于其查找操作的平均时间复杂度为O(1),这意味着无论哈希表中有多少元素,查找一个特定元素所需的时间都是常数级别的。因此,哈希表非常适合用于快速检验某个元素是否出现过,或者快速获取某个键对应的值。
3、唯一性判断: 哈希表也可以用于判断一个集合中是否包含某个元素,或者用于实现去重操作。通过将元素作为键插入哈希表,如果插入成功则表示该元素之前不存在于集合中;如果插入失败(即哈希表中已存在该键),则表示该元素已经存在于集合中。
如何使用哈希表?
1、可以使用具有哈希结构的相关STL容器。
2、可以用数组模拟简易的哈希表(如字符串中的字符、数据范围很小无负数时等等场景中)
2、两数之和(easy)
题源:链接
2.1、题解
1)、思路分析
1、暴力解法: 固定一个数,遍历找其前数,查看历史元素中,有没有能与之求和等于目标值的元素。(当然,这里也可以遍历查找当前元素之后的元素,是否有满足和为targe的。在暴力解法中这两种写法无区别,但转换到下述哈希表中,能看到不同之处。)
2、借助哈希表: 将 「数组内的元素」和「下标」 绑定在⼀起存⼊「哈希表」中,然后直接在哈希表中查找每⼀个元素的 target - nums[i]
,就能快速的找到「⽬标和的下标」。
2)、题解
借助哈希表的写法:
1、注意,这里题目说明了每种输入只会对应一个答案,说明不存在无解的情况。
2、哈希表中,查找元素的时间复杂度是
O
(
1
)
O(1)
O(1) ,遍历⼀遍数组的时间复杂度为
O
(
N
)
O(N)
O(N),相对于暴力解法的
O
(
N
2
)
O(N^2)
O(N2)可以将时间复杂度降到
O
(
N
)
O(N)
O(N),典型的以空间交换时间。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
unordered_map<int,int> hash;
for(int i = 0; i < n; ++i)
{
int aim = target - nums[i];
auto ret = hash.find(aim);//或使用hash.count()统计key值数进行判断
if(ret != hash.end())
return{i, ret->second};
hash[nums[i]] = i;
}
return {-1,-1};
}
};
暴力解法:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
for(int i = 0; i < n; ++i)
{
for(int j = i+1; j < n; ++j)
{
if(nums[i]+nums[j] == target)
return {i,j};
}
}
return {-1,-1};
}
};
3、判断是否互为字符重排(easy)
题源:链接
3.1、题解
1)、思路分析
暴力解法仍旧是固定值后遍历比较,这里不再赘述。
分析哈希表的解法:
1、首先,要知道无论哪种解法,若两个字符串的长度不相等,二者不可能互为重排,因此可直接返回 false;
2、若两个字符串能够构成互相重排,那么每个字符串中「各个字符」出现的「次数」⼀定是相同的。 因此,我们可以选择「哈希表」来统计字符串中字符出现的次数(由于这里字符串只是26个英文字母,因此直接使用数组作为哈希表即可,无需使用容器)。
这里有两方法:①对字符串s1、s2分别使用哈希表统计,再对比两哈希表中字符出现次数。②对其中一字符串使用哈希表统计,另一字符串遍历进行对比。
2)、题解
使用两个哈希表来解决的情况:
class Solution {
public:
bool CheckPermutation(string s1, string s2) {
if(s1.size()!=s2.size()) return false;//两字符串长度不相等,非重排字符串
// 使用两个哈希表来解决的情况:
int hash1[26] = {0};
int hash2[26] = {0};
for(auto ch: s1)// 遍历存入字符
hash1[ch -'a']++;
for(auto ch: s2)
hash2[ch -'a']++;
for(int i = 0; i < 26; ++i)//判断
{
if(hash1[i] != hash2[i]) return false;
}
return true;
}
};
使用一个哈希表的情况:
class Solution {
public:
bool CheckPermutation(string s1, string s2) {
if(s1.size() != s2.size()) return false;//两字符串长度不相等,非重排字符串
//使用一个哈希表的情况:
int hash[26] = {0};
for(auto ch : s1)//存放字符
hash[ch - 'a']++;
//判断:扫描第⼆个字符串,看看是否能重排
for(auto ch : s2)
{
if(--hash[ch - 'a'] < 0)//上述首行判断保证了二者字符长度相同,当减到负数时,s2中存在与s1不相等的字符
return false;
}
return true;
}
};
4、存在重复元素 I(easy)
题源:链接
4.1、题解
1)、题解
1、暴力解法: 可以对数组排个序,然后判断相邻元素是否相等,由此即可判断元素是否重复。
2、使用哈希表: 用unordered_map容器统计每一个元素出现的次数,然后查看map容器是否有元素出现的次数 >= 2。
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int> hash;
for(auto e : nums)
{
hash[e]++;//将当前元素放入哈希表中
//顺带对当前元素是否出现至少两次以上进行判断(注意:这里要先放数,再进行判断)
auto ret = hash.find(e);
if(ret != hash.end())
{
if(ret->second >= 2) return true;
}
}
return false;
}
};
3、哈希表更简化的写法: ⽆需准确统计除元素出现的数目,可以直接使用unordered_set容器,仅需在遍历数组的过程中,判断当前元素是否在之前已经出现过。
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> hash;//可以直接使用K模型
for(auto e : nums)
{
if(hash.count(e)) return true;//为真,意味着在当前元素之前,哈希表中已经存在该元素(遍历到当前位置时,元素数目为2)
else hash.insert(e);//为假,哈希表中无该元素,将其存放进入,方便后续元素判断。
}
return false;
}
};
5、存在重复元素 II(easy)
题源:链接
5.1、题解
1)、思路分析
仍旧可以使用哈希表(容器unordered_map),让数组内的元素做 key 值,该元素所对应的下标做 val 值,将「数组元素」和「下标」匹配存入「哈希表」中。
这里有一个小问题:如果在遍历过程中,遇到相同的元素(即存在多个重复元素的情况),但哈希表unordered_map中只记录了一个下标,该如何处理?
题目没有要求举例出所有情况,这里我们只需要判断一组能够满足条件的情况即可。而题目要求找满足abs(i - j) <= k
的两下标间距,这里当然是间距越小越容易达成满足的条件。而我们是从左到右的顺序遍历,完全可以舍弃先前的下标,保留最新下标位置。
2)、题解
使用unordered_map.find()
的写法:
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> hash;
for(int i = 0; i < nums.size(); ++i)
{
//找哈希表中,是否存在与当前元素相同的数值
auto ret = hash.find(nums[i]);
if(ret != hash.end())
{ //若存在,判断二者下标间距是否满足条件
int gap = abs(ret->second - i);
if(gap <= k) return true;
}
//将当前元素存放入哈希表中
hash[nums[i]] = i;//这里可覆盖原先下标,因为我们要求的是abs(i - j) <= k,与后续元素比较是,当然是间距越小越符合要求。
}
return false;//找不到的情况
}
};
使用unordered_map.count()
的写法:
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> hash;
for(int i = 0; i < nums.size(); ++i)
{
//找哈希表中,是否存在与当前元素相同的数值
if(hash.count(nums[i]))
{ //若存在,判断二者下标间距是否满足条件
int gap = abs(hash[nums[i]] - i);//mapped_type& operator[] ( const key_type& k )
if(gap <= k) return true;
}
//将当前元素存放入哈希表中
hash[nums[i]] = i;//这里可覆盖原先下标,因为我们要求的是abs(i - j) <= k,与后续元素比较是,当然是间距越小越符合要求。
}
return false;//找不到的情况
}
};
6、字母异位词分组(medium)
题源:链接
6.1、题解
1)、思路分析
互为字母异位词的单词有⼀个特点:将它们「排序」之后,两个单词是「完全相同」的。 我们可以利用这个特性,将单词按照字典序排序,如果排序后的单词相同的话,就划分到同⼀组中。
那么,哈希表中key-value值该如何设置?
①、将排序后的字符串( string
)当做哈希表的 key 值;
②、将字母异位词数组( string[]
)当成 val 值。
2)、题解
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> hash;
for (int i = 0; i < strs.size(); ++i) {
// 排序
string str = strs[i];
sort(str.begin(), str.end());
hash[str].push_back(strs[i]); // 将原字符串存放入
}
// 从哈希表中提取字符串分组返回
vector<vector<string>> ret;
for (auto& [x, y] : hash) {
ret.push_back(y);
}
return ret;
}
};