哈希表的理论基础
这里主要介绍了哈希表的三种数据结构,数组,set,map,他们都可以用来实现哈希算法。
重点要理解的是哈希函数和哈希碰撞,哈希函数本身是用来将数值对应到哈希表相应的索引下标上的,但是由于哈希函数并不是每一次都能将不同数值映射到不同的下标上,所以可能会出现哈希碰撞的现象,此时可以通过拉链法和线性探测法来解决,拉链法要考虑好哈希表的大小和数据大小的关系,既不能让空间太小使得查询时间增加,也不能空间太大造成内存资源浪费。
然后就是要把握好使用各种数据结构的时机,因此熟练掌握本小节的内容是很重要的。
有效的字母异位词
思路:本题的含义就是要求两个字符串是否能够相互组成,由于字母限定为都是小写字母,因此可以使用数组来解决,要想判断两个字符串能否相互组成,只要统计两个字符串各个字符出现的次数是否相等即可得知结果。
KEY:代码在实现的时候,不需要知道各个字母的ASCII码,只需要知道该字母减去‘a’后的值即可。
总结:本题需要知道一个字符在另一个字符串是否出现,因此使用哈希算法,又因为题目限定了只有小写字母,因此使用数组来解决。
代码:
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26]={0};
for(int i=0;i<s.size();i++){
record[s[i]-'a']++;
}
for(int j=0;j<t.size();j++){
record[t[j]-'a']--;
}
for(int i=0;i<26;i++){
if(record[i]!=0){
//说明一定有某个字符串多了或者少了字符
return false;
}
}
//如果数组中的元素都为0,说明两个字符串字母组成相同,是有效的字母异位词
return true;
}
};
两个数组的交集(不重复)
分析:求不重复的交集,首先交集,就意味着字符的重复出现,考虑使用哈希法来解决,其次数值是分散的,用数组不合适,容易浪费内存空间,因此考虑用集合来解决,multiset首先排除,它允许重复的元素出现,set它的查询效率低于unordered_set,因此最终选用unordered_set容器。
思路:可以先遍历第一个数组,将其中不重复的值存入unordered_set中,接着遍历第二个数组,如果其中的元素可以在unordered_set中找到,那么将其压入结果result当中,否则继续遍历直到遍历结束即可。
KEY:容器的选择是关键。
总结:当遇到数据量过大的情况时,不考虑使用数组,过于耗费内存,当数据量不大但是数值分散时,也不考虑使用数组,原因一样。
代码:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;
unordered_set<int> nums_set(nums1.begin(),nums1.end());
for(int num:nums2){
//C++的语法,用来遍历nums2中的元素,它可以用来遍历数组或类似容器中的元素
if(nums_set.find(num)!=nums_set.end()){
//发现nums2中的元素在nums_set中也有
result_set.insert(num);
}
}
return vector<int>(result_set.begin(),result_set.end());
}
};
快乐数
思路:题目已经说过,一个数值只有两种情况,要么是无限循环,要么是快乐数(最终返回1),因此当检测到一个元素重复出现时(不是1),就可以确定它不是一个快乐数了,考虑用哈希法来解决。首先要获取到一个数的各个位并对它们平方再求和,接着要将该结果返回给主函数,若该元素未出现过,那么压入容器,如果该元素已经出现过且不为1,那么返回false,如果该元素为1,return true。
KEY:这里容器的选择,首先可以排除数组,理由跟两个数组的交集中相同,如果使用数组,可能造成内存资源的极大浪费,又由于本题要查询的值能够尽快判断是否曾经出现过,因此使用unordered_set来保证查询的效率。
总结:本题再次使用了unordered_set容器,可以看出,它适用于存储数值分散,不要求有序,但要求不重复的情况。
代码:
class Solution {
public:
int getSum(int n){
int sum=0;
while(n){
sum+=(n%10)*(n%10);//+=
n/=10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;//因为题目说可能无限循环,因此可以通过哈希来判断一个数是否重复出现
while(1){
int sum=getSum(n);
if(sum==1){
return true;
}
if(set.find(sum)!=set.end()){
return false;
}else{
set.insert(sum);
}
n=sum;
}
}
};
两数之和
思路:本题要求返回的结果是两个数字在数组中的下标,而查询的时候使用的是数字的值,因此不能用set和数组来实现,map就刚好适合在这里登场。
KEY:
首先,map中的元素全都以键值对的形式存储,因此可以用数字的值作为KEY,数字的下标作为value
其次,当遍历到一个元素时,要想知道它所需要的数是否曾经出现过,肯定需要查询容器,使用unordered_map的效率是最高的
总结:这是map的首次使用,显示了它在存储成对元素上的优势。
代码:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//本题可以用一个指针遍历,另一个值是否出现在容器中来解决,考虑哈希表
//如果不满足条件,将该值压入哈希表中
//map用来存放遍历过的元素
std::unordered_map<int,int>map;
int s=0;
for(int i=0;i<nums.size();i++){
s=target-nums[i];
unordered_map<int,int>::iterator it=map.find(s);
if(it!=map.end()){
return {i,it->second};
}
map.insert(pair<int,int>(nums[i],i));
}
return {};
}
};