目录
哈希表理论基础
哈希表是根据关键码的值而直接进行访问的数据结构。
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:
一般哈希表都是用来快速判断一个元素是否出现集合里。
哈希函数
例如:将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
哈希碰撞
如下图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞
一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
拉链法
发生冲突的元素都被存储在其位置的链表中
线性探测法
冲突的位置,放了小李,那么就向下找一个空位放置小王的信息
常见的三种哈希结构
数组
set (集合)
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
map(映射)
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的。
使用场景
当我们要使用集合来解决哈希问题的时候:
1. 优先使用unordered_set,它的查询和增删效率是最优的;
2. 如果需要集合是有序的,那么就用set;
3. 如果要求不仅有序还要有重复数据的话,那么就用multiset。
哈希应用总结
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。同时,在遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法。
242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
说明: 你可以假设字符串只包含小写字母。
哈希法
bool isAnagram(string s, string t) {
int hash[26] = {0};
// 统计s中的字符出现次数
for(int i = 0; i<s.size(); i++){
hash[s[i]-'a']++;
}
// 统计t中的字符出现次数并逐一删减
for(int i = 0;i<t.size(); i++){
hash[t[i]-'a']--;
}
// 判断数组中的值是否全部为0
for(int i = 0;i<26; i++){
if(hash[i]!=0){
return false;
}
}
return true;
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
349. 两个数组的交集
unordered_set解法
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());
}
- 时间复杂度: O(mn)
- 空间复杂度: O(n)
202. 快乐数
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> result;
while(1){
int sum = get_sum(n);
if(sum == 1){
return true;
}
if(result.find(sum)!=result.end()){
return false;
}
else{
result.insert(sum);
}
n = sum;
}
}
};
- 时间复杂度: O(logn)
- 空间复杂度: O(logn)
1. 两数之和
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
本题有四个重点:
为什么会想到用哈希表: 当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。那么我们就应该想到使用哈希法了。
哈希表为什么用map: 本题,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
map是用来存什么的: map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
map中的key和value用来存什么的: 判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
map法
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 {};
}
- 时间复杂度: O(n)
- 空间复杂度: O(n)
笔记参考: 代码随想录-map