一、哈希表基础
文章讲解: 代码随想录
1.当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。牺牲了空间换取了时间
2.官方解释:哈希表是根据关键码的值而直接进行访问的数据结构。
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素
3.时间复杂度从枚举的O(n)到通过索引直接查询的O(1)
4.哈希函数:将存放的元素映射到索引上,就可以通过查询索引下标快速判断一个元素是否在集合里。hashcode通过特定编码方式,可以将其他数据格式转化为不同的数值。(取模)
具体例子看上文链接:
5.哈希碰撞:由于哈希函数取模或元素数量过多等情况导致映射到同一个下标时,哈希碰撞
拉链法:碰撞后,都存放在同一个下标处(链表)
线性探测法:碰撞后,向下寻找空位存放(哈希表要大)
周末看看是如何代码实现的
6.数组:缺点:哈希值未知,哈希值比较少、特别分散、跨度非常大时不建议使用数组(需要申请比较大的数组空间间并会出现浪费),哈希函数可能是自己写的
7.set(集合):数量未知,根据有序,无序,是否重复选择使用
set是一个集合,里面放的元素只能是一个key
可以认为unordered_set 存储的都是键和值相等的键值对,为了节省存储空间,该类容器在实际存储时选择只存储每个键值对的值。即哈希映射的索引为值。
缺点:直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
8.map(映射)map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
详细的区别:代码随想录
二、有效的字母异位词
题目链接/文章讲解/视频讲解: 代码随想录
1.哈希映射函数 :s[i]-'a' 将每一个元素映射到数组下标(索引)处 字母到数组索引可以用ascii值
本题可以通过减去一个相对值'a'来建立,一开始我还是用ascii值26来处理
2.查找元素:时间复杂度O(1),通过索引直接查找
3.还可以使用multiset完成
for(int j=0;j<t_size;j++)
{
array[t[j]-'a']--;
}
代码:数组
class Solution {
public:
bool isAnagram(string s, string t) {
int s_size=s.size();
int t_size=t.size();
int array[26];
for(int i=0;i<s_size;i++)
{
array[s[i]-'a']++;
}
for(int j=0;j<t_size;j++)
{
array[t[j]-'a']--;
}
for(int i=0;i<26;i++)
{
if(array[i]!=0)
{
return false;
}
}
return true;
}
};
三、两个数组的交集
题目链接/文章讲解/视频讲解: 代码随想录
1.比较简单,就是存入哈希表,再查找判断
2.时间复杂度: O(n + m) m
3.使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复
4.本题因为数据有范围了使用了数组
5.数组去重
for (int num : nums1)
{
hash[num] = 1;//数组去重操作
}
代码:set
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
unordered_set<int> hash;
for(int i=0;i<nums1.size();i++)
{
hash.insert(nums1[i]);
}
for(int i=0;i<nums2.size();i++)
{
if(hash.find(nums2[i])!=hash.end())
{
result.insert(nums2[i]);
}
}
return vector<int>(result.begin(),result.end());
}
};
代码:数组
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
int hash[1001] = {0};
for (int num : nums1) {
hash[num] = 1;//数组去重操作
}
for (int num : nums2) {
if (hash[num] == 1) {
result.insert(num);//结果去重
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
四、快乐数
题目链接/文章讲解: 代码随想录
1.如果单单是按数学逻辑写的话,会出现时间超出限制,因为会出现无限循环问题
无限循环终止条件:出现相同的sum值(查找是否有元素使用哈希表)
2.这题显然不考虑使用数组,使用set比较方便
代码
class Solution {
public:
int getsum(int n)
{
int sum=0;
while(n)
{
sum+=(n%10)*(n%10);
n=n/10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> result;
while(1)
{
int sum=getsum(n);
if(sum==1)
{
return true;
}
if(result.find(sum)!=result.end())
{
return false;
}
else
{
result.insert(sum);
}
n=sum;
}
}
};
五、两数之和
题目链接/文章讲解/视频讲解: 代码随想录
1.本题需要查找元素是否在集合内(在该数组中找出 和为目标值 target
的那 两个 整数)
2.数组的缺点:.数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set的缺点:set是一个集合,里面放的元素只能是一个key。本题需要返回元素下标,所以不合适
所以本题以上两种方法都不合适,采用map
3.需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
4.这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
5.代码的逻辑:先查询再插入,边查询边插入(查询这个元素的前是否有满足要求的另一个数),(本题答案是唯一 的)这样可以避免(如6=3+3)相同key值的情况,下标不一样的情况(一个在map中一个正在通过vector访问)
也可以先全部插入再查询,使用multimap,比较繁琐。
代码:
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 iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};//map中的元素在i之前
}
// 插入
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};