学习链接:哈希表理论基础
哈希表用于快速判断一个元素是否出现在集合里
哈希函数:将其他类型数据格式转化为不同的数值(数据和索引的映射)
哈希碰撞:不同的数据映射到同一索引上
发生冲突的元素存储在链表中
选择适当的哈希表大小,使得既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间
向下找一个空间存放冲突的元素,要求tableSize>dataSize
数组,set,map
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找
题目链接:242.有效的字母异位词
文章讲解:代码随想录|242.有效的字母异位词
视频讲解:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词
判断字符串的每一个字符出现在字符的集合(26个字母)中的次数,用哈希表
这里的集合大小固定且数量较小,因此用简单结构——数组,相比于set运算更快
第一个字符串s在哈希表中record[s[i] - ‘a’]++
第二个字符串t在哈希表中record[s[i] - ‘a’]–
如果最终哈希表中仍然全是0,则每个字符出现的次数一样,判定两个字符串为字母异位词
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};
题目链接:349. 两个数组的交集
文章讲解:代码随想录|349. 两个数组的交集
视频讲解:学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集
如果这道题没有限制数值的大小,则无法使用数组,而且如果哈希值比较少,又跨度非常大,用数组会造成极大的空间浪费
此时需要用set,set在c++中提供了三种可用的数据结构:set, multiset, unordered_set
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的(在树中搜索效率低),并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set
题目要求结果没有重复的值且无序,因此也可以用unordered_set来存储result,记得返回值要改回成题目要求的数据格式
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
注:如果nums_set.find(num)没找到num则会返回nums_set.end();for(int num:num2)这个用法可以用于任何支持迭代器的容器
题目链接:202. 快乐数
文章讲解:代码随想录|202. 快乐数
如果始终变不到1,则说明进入了无限循环,也就是这个平方和sum会重复出现,所以可以把sum保存在一个集合里,每次求出来一个sum就查找一下,如果找到了就说明有重复的,会进入无限循环
显而易见,可以用哈希表进行查询
此题的重点是get到无限循环中sum会重复出现
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;
}
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
要先判断该sum是否出现,然后再把这个sum放到集合里,如果先放到集合里再判断,则一定会查询到,这样的话就一定会return false
犯了个低级错误,写代码前一定要思考好其中的逻辑再写!
题目链接:1. 两数之和
文章讲解:代码随想录|1. 两数之和
视频讲解:梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!
当遍历第i个元素的时候,看之前遍历的元素集合中是否存在target-nums[i],如果存在则返回两个下表,否则继续往下遍历
显而易见,用哈希法
又因为找的是元素值,返回的是下标,有两个元素,因此用map(key,val),
因为要通过元素值来判断,因此key存储元素值,返回的是元素值的下标,因此val存储下标
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++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
注意map的一些操作写法
2023.08.16补充:map也可以像数组一样写,例如map[key]=value,代码可见day7的第一题