2023/12/4 首次接触哈希表
哈希表基础知识
哈希表(Hash table,也称散列表):哈希表是根据关键码的值而直接进行访问的数据结构
数组就是一张哈希表,关键码:数组下标,通过下标访问数组元素
哈希表解决什么问题:
一般哈希表用来快速判断一个元素是否出现在集合里
举例理解:比如要查询一个名字是否在这所学校里,要枚举的话,时间复杂度为O(n),hash table只需O(1)。需要把学生信息存在哈希表中,查询时通过索引直接可以查询。将学生姓名映射到哈希表上就会涉及到hash function哈希函数。
哈希函数:通过hashCode把名字转化为数值
哈希函数会涉及哈希碰撞(两种解决办法:拉链法;线性探测法)
常见哈希结构
- 数组
- 集合(set)
- 映射(map)key value的数据结构
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,只能删除和增加
映射 | 底层实现 | 是否有序 | 数值是否可以重复实现 | 能否更改数值 | 查询效率 | 增删效率 |
std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(logn) | O(logn) |
std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
当我们使用集合set来解决哈希问题时,优先使用unoedered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,就用set; 有序且要求数据重复则使用multiset
总结:
当我们遇到要快速判断一个元素是否出现在集合里的时候,就要考虑哈希法。
if(set.find(element) != set.end())
但哈希法也是牺牲了空间换取时间,因为我们要使用额外的数组,set或者map来存放数据,才能实现快速的查找
该代码可以借鉴的地方是当以后需要统计字符出现的次数时,可以用到
record[]++
- 遇到的另一个问题数组时c++原生数据结构,不可以record.size(),求数组长度需要sizeof(record)/sizeof(record[0])
- 之前逻辑不通的地方,是函数中一旦执行return就会结束函数的运行,后面的代码不会执行
class Solution{
public:
bool isAnagram(string s, string t)
{
int record[26]={0}; // 初始化数组record
// 遍历字符串s
for (int i = 0; i < s.size(); i++)
{
// 此段非常巧妙,将字母之间的ASCII递增值,差值转换为哈希函数
record[s[i]-'a']++; // 对数组索引为(s[i]-'a')的元素值,进行递增操作
}
for (int j = 0; j < t.size(); j++)
{
record[t[j]-'a']--;
}
// 再次遍历record数组
for(int i = 0; i < record.size() ; i++)
{
if (record[i] != 0)
{
return false;
}
}
// 如果record数组中所有元素全部为0,则说明s和t是字母异位词
return true;
}
}
set : set, multiset; unorderset
学会使用哈希数据结构:unordered_set, 无序不重复
数组来做哈希表的限制:数值比较小,不可以跨度过大
学习点:
(1)如何构建无序集合
unordered_set<int> nums_set(nums1.begin(),nums1.end());
(2)循环无需索引,采用范围法遍历
(3)哈希表的核心 if(nums_set.find(num)!= nums_set.end())
(4) 如何插入一个元素set.insert(nums)
class Solution
{
public:
// 成员函数 intersection 用于计算数组交集
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
// 存放结果的无序集合,使用set是为了去重
unordered_set<int> result_set;
// 将nums1转换为无序集合 nums_set,方便快速查找
// 这行代码是将nums1中的元素去重,构建一个无序集合nums_set
unordered_set<int> nums_set(nums1.begin(), nums1.end());
// 遍历nums2中的每一个元素
for (int num:nums2)
{
// 如果当前元素在nums_set中出现过,则加入结果集
if (nums_set.find(num) != nums_set.end()) //nums_set.find(num)用于检查当前迭代的元素num是否在无序集合nums_set中,若存在则返回该元素,若不存在则指向集合末尾,代码中!=nums_set.end()则表明,返回值不指向末尾
{
result_set.insert(num);// 在result_set中插入num,即交集元素
}
}
// 将结果集转换为向量并返回
return vector<int>(result_set.begin(), result_set.end());
// 这行代码使用 result_set.begin() 和 result_set.end() 定义了一个迭代器范围,然后通过 vector<int> 构造函数将这个范围内的元素复制到一个新的整数向量中。最终,这个新的向量被作为函数的返回值。这样做的目的是将存储在 result_set 中的交集元素以向量的形式返回,方便函数的调用者使用和处理。
}
};
数组解法
class Solution
{
public:
// 成员函数 intersection 用于计算两个整数向量的交集
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
// 存放结果的无序集合,使用set是为了给结果集去重
underordered_set<int> result_set;
//数组hash 用于记录nums1中每个元素的出现情况,默认数值为0
int hash[1005] = {0};
//遍历nums1中的每一个元素,将其在hash中标记为1
for (int num:nums1)
{
hash[num] = 1;
}
// 遍历nums2中的每个元素
for (int num:nums2)
{
if (hash[num] == 1)
{
result_set.insert(num);
}
}
// 将结果集合转换为向量并返回
return vector<int>(restult_set.begin(),result_set.end());
}
};
此题核心点:求和&判断
求和:记忆(1)如何取个位数n%10 ; (2) 如何去掉最低位数值 n/=10;
判断:记忆(1)如何写无限循环while(1); (2)如何判断数在set中 if(set.find(sum) != set.end());
(3) set中插入元素 set.insert();
class Solution
{
public:
// 取数值各个位上的单数之和
int getSum(int n)
{
int sum = 0;
// 循环迭代,取每个位上的数字的平方和
while(n)
{
sum += (n % 10) *(n % 10); // n%10是为了取得n的最低位数字
n /= 10; // 将n更新为去掉最低位的数值
}
// 返回计算得到的平方和
return sum;
}
bool isHappy(int n)
{
unoredered_set<int> set;
// 无限循环,直到满足退出条件
while(1)
{
//获取当前数的各个位上的单位之和
int sum = getSum(n);
if (sum == 1)
{
return true;
}
//
if (set.find(sum) != set.end())
{
return false; // 如果在set中发现重复sum,则说明陷入死循环
}
else
{
set.insert(sum); // 更新set
}
// 更新当前数为平方和
n = sum;
}
}
};
什么时候使用哈希法:当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就第一时间想到哈希法。
本题,我们不仅要知道元素有没有遍历,还要知道这个元素对应的下标,需要key value结构来寻访,key存元素,value存下标
class Solution
{
public:
vector<int> twoSum(vector<int>& nums, int target)
{
std::unordered_map <int,int> map; // 创建一个无序映射map,用于存储键值对,
// 键,值都是 int 类型
// 遍历数组
for(int i = 0; i < nums.size(); i++)
{
auto iter = map.find(target - nums[i]); // 在无序map中查找target-nums[i]的元素
// auto 关键字用于自动推断迭代器的类型
// iter 指向该元素的位置,否则指向映射的末尾map.end()
if (iter != map.end())
{
return {iter->second,i}; // 创建一个包含两个元素的整数数组
}
//如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int,int>(nums[i],i)); // pair 表示一个键值对的对象
}
}
}
以上题目的关键点都在
set.find(num) != set.end() 某个元素是否在集合中,即哈希表的关键核心