哈希表理论基础
哈希表(Hash table)
1.什么是哈希表:哈希表是根据关键码的值而直接进行访问的数据结构。(数组就是一张哈希表)
2.哈希表能解决什么问题:一般哈希表都是用来快速判断一个元素是否出现在集合里。
哈希函数(hash function)
1.哈希函数:把数据直接映射为哈希表上的索引。index=hashFunction(数据)
2.通过hashCode可以将其它数据格式转化为不同的数值,这样就可以把数据映射为哈希表上的索引数字了。如果hashCode得到的数值大于哈希表的大小(tableSize),我们会再次对hashCode得到的数值进行一个取模操作(hashFunction=hashCode(数据)%tableSize),这样可以保证数据一定可以映射到哈希表上。
哈希碰撞
1.哈希碰撞:当数据的数量大于哈希表大小,这时几个不同的数据就会映射到哈希表同一个索引下标的位置,这个现象就叫做哈希碰撞。
2.哈希碰撞解决方法
(1)拉链法(链表法):当发生哈希碰撞时,将具有相同哈希值的数据存储在同一个槽位中,形成一个链表。虽然这种方法可以减少碰撞,但也会增加内存使用。
(2)线性探测法(开放寻址法):当发生哈希碰撞时,线性探测法会尝试寻找下一个空闲的槽位来存储数据,这可能会增加碰撞的概率,所以tableSize一定要大于dataSize。
3.常见三种哈希结构
(1)常见三种哈希结构:数组、set、map。
(2)map和set,底层实现红黑树,有序,数值不可重复,增删查O(logn)。
multiset和multimap,底层实现红黑树,有序,数值可以重复,增删查O(logn)。
unordered_set和unordered_map,底层实现哈希值映射的方式,无序,数值不可重复,增删查O(1)。
(3)遍历方式
set
//第一种迭代器for循环遍历
auto it=set.begin();
for(;it!=set.end();++it) {}
//第二种范围for循环
for(int v:set) {}
map
//第一种迭代器for循环遍历
auto it=map.begin();
for(;it!=map.end();++it) {}
//第二种范围for循环遍历
for(const pair<int,int> &p:map) {}
有效的字母异位词 leetcode 242
class Solution {
public:
bool isAnagram(string s, string t) {
int hash[26]={0};
for(int i=0;i<s.size();i++){
hash[s[i]-'a']++;
}
for(int j=0;j<t.size();j++){
hash[t[j]-'a']--;
}
for(int k=0;k<26;k++){
if(hash[k]!=0) return false;
}
return true;
}
};
字符串每一位字母与'a'ASCⅡ码的差值映射为hash[26]的下标索引,以此记录字符串中某个字母出现的次数。
两个数组的交集 leetcode 349
解法一:数组双指针思想
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> result;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int i=0,j=0;
while(i<nums1.size()&&j<nums2.size()){
if(nums1[i]==nums2[j]){
if(result.empty()||result.back()!=nums1[i]){
result.push_back(nums1[i]);
}
i++;
j++;
}else if(nums1[i]<nums2[j]){
i++;
}else j++;
}
return result;
}
};
总结
1.先对两个数组从小到大排序,i指针和j指针分别指向两个数组的起始位置,然后判断nums1[i]和nums2[j]的大小,如果相等则将nums1[i]放入结果,之后两个指针同时向后移动一位,如果nums1[i]<nums2[j],因为数组是从小到大排序的,那么就让i指针向后移动,使nums1[i]变大,去找与nums2[j]相等的数。
2.result去重:因为数组都是从小到大有序排列的,那么按照1中双指针遍历方法得到的结果也是有序的,我们只要判断当前符合nums1[i]==nums2[j]条件的数是不是与结果容器中末尾元素相同即可,如果不同则存入结果,如果相同则不存,两个指针同时向后移动继续遍历。
3.C++中,不能直接使用变量来定义数组的大小,数组的大小必须是一个常量表达式,即在编译时就能确定的值。当你想要使用一个数组来存放结果但最初又不知道这个数组有多大的时候,我们可以使用动态分配内存的方式来创建数组,比如vector容器。
解法二:set解决
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> set(nums1.begin(),nums1.end());
unordered_set<int> result;
for(int j=0;j<nums2.size();j++){
if(set.find(nums2[j])!=set.end()){
result.insert(nums2[j]);
}
}
return vector<int>(result.begin(),result.end());
}
};
出现的错误
不能直接返回result,因为intersection函数的返回值是一个vector<int>类型。
解法三:数组加set
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
int hash[1005]={0};
unordered_set<int> result;
for(int i=0;i<nums1.size();i++){
hash[nums1[i]]=1;
}
for(int j=0;j<nums2.size();j++){
if(hash[nums2[j]]==1){
result.insert(nums2[j]);
}
}
return vector<int>(result.begin(),result.end());
}
};
快乐数 leetcode 202
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;
}
}
};
总结
输入一个正整数n,求n每为数字平方的和,然后再求和的每位数字平方和,一直循环如果等于1就是快乐数,也就是说循环到重复的结果,那么就可以想到使用unordered_set来存放每一次的平方和,然后来检查有没有重复出现。
出现的错误
while循环中set.insert(sum),应该在if(set.find(sum)!=set.end())判断之后。我写成了之前。
set用来存放我们遍历过的平方和,所以先判断,一开始set为空这个时候肯定没有,就可以通过循环一步步存放元素了。
两数之和 leetcode 1
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> map;
for(int i=0;i<nums.size();i++){
auto it=map.find(target-nums[i]);
if(it!=map.end()){
return {it->second,i};
}else{
map.insert(pair<int,int>(nums[i],i));
}
}
return {};
}
};
总结
1.要查询目标值最后还要返回下标,所以想到使用map的键值对来存放,因为数据是无序且不能重复的,所以使用unordered_map。
2.这个题和上一题快乐数有些相同之处,快乐数的set用来存放遍历过的平方和,这一题map用来存放遍历过的数组元素以及对应下标,所以还是先判断,再存放。