一、基础概念学习
哈希表
哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器。
哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重。
哈希函数
哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道这位同学是否在这所学校里了。
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。
此时问题又来了,哈希表我们刚刚说过,就是一个数组。
如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。
哈希碰撞
如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞。
拉链法
(数据规模是dataSize, 哈希表的大小为tableSize)
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
线性探测法
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
常见哈希结构数组、set(集合)、map(映射)
today task
1.有效的字母异位词
所谓异位词是指两个字符串字母元素和个数相同,排列顺序不同(这里顺序相同也是)。这里的字符串都是小写字母,而小写字母的个数总共26个,且它们都有一一对应的ASCII值,我们可以利用哈希表(定义一个大小为26的数组)将这些元素映射为0-25,对应a-z。然后我们在遍历的时候,只需要统计它们的下标值即可。具体地,利用数组记录下字符串1中的每个元素对应的下标值,如Hash[s[i]-‘a’]++,再遍历另一个字符串,遇到相同的元素则Hash[s[i]-‘a’]–,如果是异构的话最后统计的下标值一定都是0。
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 i=0;i<t.size();i++)
{
hash[t[i]-'a']--; //数组下标--
}
for(int i=0;i<26;i++)
{
if(hash[i]!=0)
{
return false;
}
}
return true;
}
};
2.两个数组交集
寻找两个数组相交的元素
方法一、set方法,使用unordred_set,unordred_set容器自带去重特性。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;//存放结果,之所以用set是因为set会自动去重
unordered_set<int> num_set(nums1.begin(),nums1.end()); //把nus1的数据存到set集合里面
for(int num:nums2)//遍历nums2
{
if(num_set.find(num)!=num_set.end())//在num_set中寻找元素num,如果没找到则返回end()
{
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
关于容器中相关函数的解释
这是标准库里迭代器部分的内容,简单点说,就是用find这个函数,去找str这个序列中的i元素,如果序列中所找的这个元素不存在,就会返回end()。
那么按着这个思路去理解这两行命令就很容易了!
如果str.find(i)返回的不是str.end(),就说明在str序列中找到i元素:
str.find(i) != str.end() //说明找到了
同理,如果str.find(i)返回的是str.end(),就说明在str序列中没找到i元素:
str.find(i) == str.end() //说明没到
这个地方跟正常的理解逻辑相反,注意区分。
————————————————
版权声明:本文为CSDN博主「恋蛩音」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_17846375/article/details/104814813
方法二、由于本题的数组大小在0-1000,我们也可以用哈希表的数组方法来解决。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set;//存放结果,之所以用set是因为set会自动去重
int hash[1005]={0};
for(int num:nums1)
{
hash[num]=1; //遍历vector1,记录所有出现的元素
}
for(int num:nums2)
{
if(hash[num]==1)
{
result_set.insert(num);//遍历vector2,记录相同元素
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
3.快乐数
题目的关键:sum会重复出现
关键步骤:
1.平方求和问题
2.情况1;结果为1,返回true
情况2:无限循环,利用哈希表判断
class Solution {
public:
int get_sum(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 = get_sum(n);
if(sum == 1)
{
return true;
}
if(set.find(sum) != set.end())
{
return false; //如果在set中找到了相同的sum,说明陷入了无限循环
}
else
{
set.insert(sum);
}
n=sum;//进入下一次循环
}
}
};
4.两数之和
如题意,在数组中找到两个等于目标值的元素的下标。
可以用暴力解法,两个for循环嵌套。
在学习了哈希表之和,理应想到用哈希表解决此题。
思路:nums[i]+nums[j]=target
利用nums[i]和target寻找到j
map结构为key和value,key寻找过的元素值,value存储其下标。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map<int,int> map;
for(int i=0;i<nums.size();i++) //遍历数组
{
unordered_map<int,int>::iterator iter = map.find(target - nums[i]);//寻找另一个值
if(iter!= map.end()) //如果找到了
{
return {iter->second,i};//返回下标
}
map.insert(pair<int,int>(nums[i],i));//如果没有找到则将当前遍历的元素加入map中
}
return {};
}
};