哈希表(散列表)是根据关键码的值而直接进行访问的数据结构。
相比于数组, 哈希表通过哈希函数, 将一个元素根据其内容映射到哈希表中, 从而实现元素的快速查找.
哈希函数通过编码的方式进行映射, 上述例子中是通过多次取模的操作, 将地址取到tableSize的范围内.
但是如果有多个学生姓名取到了同一位置怎么办?
这就涉及到哈希碰撞的解决.
存放和查找小王的时候, 如果发现小李了, 就依次向下一个位置查找/存放.
此时要求tableSize>dataSize, 不然没地方存放冲突数据.
想要使用哈希法解决问题的时候, 一般选择使用数组, 集合(set), 映射(map)
在以上的这些set和map中, 一般使用unordered_set和unordered_map, 因为他们的**查询与增删效率都是O(1)**的
一般哈希表都是用来快速判断一个元素是否出现集合里.
所以, 在需要频繁判断一个元素是否在集合中时, 就考虑哈希法
(或者判断一个元素是否出现过)
同时因为哈希法本质是牺牲空间换时间, 所以更适合对于空间要求不高, 但是对时间要求高的场景.
> 参考文章:https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
LeetCode链接:https://leetcode.cn/problems/valid-anagram/
unordered_map
记录字母出现频率思路: 用unordered_map
class Solution {
public:
bool isAnagram(string s, string t) {
unordered_map<char, int> sSet, tSet;
for(char c : s){
sSet[c]++;
}
for(char c : t){
tSet[c]++;
}
for(char c : s){
if(sSet[c]==tSet[c])
continue;
else
return false;
}
for(char c : t){
if(sSet[c]==tSet[c])
continue;
else
return false;
}
return true;
}
};
可以使用一个26位的数组实现记录字母出现频率的功能.
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.length() != t.length())
return false;
vector<int> counter(26, 0);
for(char c : s){
counter[c-'a']++;
}
for(char c : t){
counter[c-'a']--;//这里其实很奇妙
if(counter[c-'a']<0)
return false;
}
return true;
}
};
代码中说那里很奇妙, 原因是:
这里似乎只考虑了t中某字母出现次数比s中多的情况, 没有考虑t中同一字母少的情况.
但其实是没有问题的
.
因为刚开始时判断过两个字符串长度相同, 那么当两个字符串中字母频率不同时, 就一定是某个字母多了, 某个字母少了, 那么就一定会被检测出来.
真妙啊.
LeetCode链接:https://leetcode.cn/problems/intersection-of-two-arrays/
思路: 使用unordered_set
, 在insert
的过程中自动就去重了.
具体用set1
记录nums1
中的元素;
遍历nums2
, 发现重复元素就加入set2
;
最后将set2
转化为vector
输出
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> set1, set2;
for(int num : nums1){
set1.insert(num);
}
for(int num : nums2){
if(set1.count(num))
set2.insert(num);
}
// vector ans;
// for(auto it : set2)
// ans.push_back(it);
// return ans;
return vector<int>(set2.begin(), set2.end());
}
};
LeetCode链接:https://leetcode.cn/problems/happy-number/description/
题目本身不难, 但是其数学原理难以想到
数学原理讲解
class Solution {
public:
int multiple(int n){
int ans=0;
while(n){
ans += (n%10) * (n%10);
n /= 10;
}
return ans;
}
bool isHappy(int n) {
unordered_set<int> set;
while(!set.count(n)){
// cout << "n= " << n << endl;
if(n==1)
return true;
set.insert(n);
n = multiple(n);
}
return false;
}
};
截图
LeetCode链接:xxx(记得加点击跳转链接)
两层循环, O(n^2)求解
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 {};
}
};
unordered_map
很容易想到的一种解法是:
先建立unordered_map<值, 下标>
, 然后依次遍历, 在遍历到值为val的时候, 查找值为target-val的元素.
但是这样有个问题, 那就是当val=target/2
时, 会查到自己, 然后就出问题了.
所以不能从一开始就建立哈希表, 要在遍历的过程中逐步建立, 即:
遍历过程中, 对于当前元素, 如果能在哈希表中找到target-val
的对应元素, 就返回{i, map[target-val]}
;
没找到的话, 就将当前元素加入map
.
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n=nums.size();
unordered_map<int,int> map;
for(int i=0; i<n; ++i){
if(map.count(target-nums[i])){
return {i, map[target-nums[i]]};
}else{
map[nums[i]] = i;
}
}
return {};
}
};
对于哈希表的问题, 要牢记: 大量的检测元素是否存在时, 使用哈希表.
并且在使用unordered_map
时, 需要考虑用什么当key
, 以及val
到底存储什么.
二者都可以用于判断哈希表中有无某元素.
find()
返回一个有效的迭代器,我们可以使用它来访问该元素。如果未找到元素,则 find()
返回 mySet.end()
,并且我们可以根据这一结果输出相应的消息。
相比之下,count()
只返回元素出现的次数,并不提供具体的位置或迭代器。如果你只是想知道元素是否存在并不需要具体的位置信息,那么使用 count()
是可以的。但如果你希望获取元素的位置或进行后续的操作,使用 find()
和 end()
更加灵活。
总结起来,使用 set.find() != set.end()
而不是 set.count()
的原因是为了获取更多的操作灵活性,包括访问元素以及处理元素不存在的情况。
如果只是想要检测一下有没有这个元素存在, 那么count()
足够了.
本文参考:
242.有效的字母异位词
349. 两个数组的交集
202. 快乐数
1. 两数之和