哈希表的基本概念
1. 定义:一般哈希表都是用来快速判断一个元素是否出现集合里,但是哈希法也是牺牲了空间换取了时间。将元素映射到哈希表上就涉及到了hash function ,也就是哈希函数。
2. 解决哈希碰撞的方式是拉链法(用链表)和线性探测法。
3. 实现方式:
红黑树是key有序的平衡二叉树。优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
242.有效的字母异位词
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
答案链接:代码随想录
思路:
这道题属于查找,可以想到用哈希表进行。这道题可以利用数组作为哈希表。
解答:
将26个英文字母的ASCII码作为key,查找ASCII码的方法是用字符与'a'作差。
bool isAnagram(string s, string t) {
int record[26] = {0};
for(int i=0; i<s.size(); i++){
record[s[i] - 'a'] ++ ;
}
for(int i=0; i<t.size(); i++){
record[t[i] - 'a'] -- ;
}
for(int i=0; i<26; i++){
if(record[i] != 0){
return false;
}
}
return true;
}
349. 两个数组的交集
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
答案链接:代码随想录
思路:
这道题同样需要使用哈希表,但是数组不太行,因为不知道最大的元素是什么。即使通过遍历得知了最大元素,哈希表也会比较稀疏。因此想到使用链表,但是链表因为索引的时间复杂度为O(n)并不能充当哈希表。
解答:
1. 容器的选择:
使用数组来做哈希的题目,是因为题目都限制了数值的大小。如果哈希值比较少、特别分散、跨度非常大,就要使用另一种结构体了,set。关于set,C++ 给提供了如下三种可用的数据结构:
- std::set:红黑树,key有顺序
- std::multiset:红黑树,key有顺序,元素可以重复
- std::unordered_set:哈希表,key无顺序,读写效率最高
根据这道题的需求,我们不需要让key有顺序,因此使用unordered_set容器。
2. 容器的使用:
(1)容器的复制
nums1.begin()
和 nums1.end()
是指向容器 nums1
的迭代器。nums1
是一个 vector<int>
类型的对象。这两个函数通常用来获取容器的起始和结束位置的迭代器。nums1.begin()
返回指向第一个元素的迭代器,如果 vector
为空,则 nums1.begin()
的值等同于 nums1.end()
。nums1.end()
是一个指向 vector
中最后一个元素之后位置的迭代器,它并不指向任何有效的元素,而是用于标记容器的结束。当这两个迭代器一起使用时,它们通常表示容器的整个范围。例如,在代码 unordered_set<int> nums_set(nums1.begin(), nums1.end());
(2)容器的元素查找
在 C++ 中,nums_set.find(num)
尝试在 nums_set
中找到一个值为 num
的元素。如果找到了这个元素,find
方法返回一个指向该元素的迭代器。如果没有找到,它返回一个指向 unordered_set
结束位置的迭代器,即 nums_set.end()
。
(3)容器的遍历
for (int num : nums2)
是一个范围基于的 for 循环(也称为基于范围的 for 循环或范围 for 循环),简洁地遍历容器(如数组、向量、集合等)中的所有元素。
代码:
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){
if(nums_set.find(num)!=nums_set.end()){
result_set.insert(num);
}
}
return vector<int>(result_set.begin(),result_set.end());
}
202. 快乐数
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目解答:代码随想录
思路:
这道题的关键在于判断什么情况下会陷入无限循环。这里无限循环可以用哈希表来实现。
感悟:
识别无限循环的方法至今有两种,一种在链表中,用快慢指针识别。这里快慢指针还可以用来判断循环开始的位置。这里对循环节没有限制。
另一种是哈希表。但这种方式需要循环节内部没有重复元素。换句话说,循环起始位置就可以识别循环的开始。对于这道题,哈希表是更好的方式。
代码:
bool isHappy(int n) {
unordered_set <int> exist_set;
int cal_num = n;
do{
int cal = 0;
do{
cal+=(cal_num%10)*(cal_num%10);
cal_num/=10;
}while(cal_num);
if(exist_set.find(cal)==exist_set.end()){
exist_set.insert(cal);
cal_num = cal;
}else{
return false;
}
}while(cal_num!=1);
return true;
}
1. 两数之和
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
答案链接:代码随想录
思路:
这道题的思路是哈希表,但是需要dictionary的数据类型。每次从字典中找对称元素。
解答:
(1)什么时候使用哈希法:当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
(2)字典数据类型map:
(3)字典的插入:
map.insert(pair<int, int>(nums[i], i))
这行代码用于向 map
中插入一个新的键值对。pair<int, int>(nums[i], i)
: 这创建了一个 std::pair
对象,它是一个将两个值组合成一个单一对象的简单容器。
代码:
vector<int> twoSum(vector<int>& nums, int target) {
std::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.insert(pair<int, int>(nums[i], i));
}
return {};
}