一、基础知识
https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E5%B8%B8%E8%A7%81%E7%9A%84%E4%B8%89%E7%A7%8D%E5%93%88%E5%B8%8C%E7%BB%93%E6%9E%84
1、哈希表的定义
哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。
哈希表是根据关键码的值而直接进行访问的数据结构。
这么官方的解释可能有点懵,其实直白来讲其实数组就是一张哈希表。哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
那么哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里。
例如要查询一个名字是否在这所学校里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数。
2、哈希函数
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,这样我们就保证了学生姓名一定可以映射到哈希表上了。
此时问题又来了,哈希表我们刚刚说过,就是一个数组。
如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。
接下来哈希碰撞登场
3、哈希碰撞
(1)拉链法
(2)线性探测法
4、哈希表常用的三种数据结构
(1)数组
一般题中涉及到的数值较小,且数值的范围可控也较小时,有数组即可。
(2)set(集合)
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
(3)map(映射)
映射map 是一个key-value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。map 能在最快的时间内查找某个 key 是否在这个map里出现过。
5、总结
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
二、两个数组的交集(P349)
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
//定义set集合
Set<Integer> set1 = new HashSet<>();
Set<Integer> ans = new HashSet<>();
//增强for循环
for(int i : nums1){
set1.add(i);//向集合中添加元素
}
for(int i : nums2){
if(set1.contains(i)){//判断集合中是否含有某个元素i
ans.add(i);
}
}
//方法1:将ans集合转化为数组
return ans.stream().mapToInt(x -> x).toArray();
//方法2:申请一个数组存放集合ans中的元素,最后返回数组
int[] ans1 = new int[ans.size()]; //求集合ans的长度:ans.size()
int j = 0;
for(int i : ans){
ans1[j++] = i;
}
return ans1;
}
}
三、两数之和(P1)
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] ans = new int[2];
Map<Integer, Integer> map = new HashMap<>(); //定义一个map
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i];
if(map.containsKey(temp)){ //判断map是否包含temp这个key值
ans[0] = i;
ans[1] = map.get(temp); //取map中key值为temp的所对应的value值
break;
}
map.put(nums[i], i); //将key值nums[i]和value值i放入map
}
return ans;
}
}
四、四数相加(P454)
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值defaultValue。
getOrDefault() 方法的语法为:
hashmap.getOrDefault(Object key, V defaultValue)
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>();//定义一个map
int count = 0;
//统计两个数组中的元素之和,同时统计出现的次数,放入map
for(int i : nums1){
for(int j : nums2){
int sum = i + j;
map.put(sum, map.getOrDefault(sum, 0) + 1);//将sum及其出现的次数放入map
}
}
//统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
for(int i : nums3){
for(int j : nums4){
int temp = 0 - (i + j);
count = count + map.getOrDefault(temp, 0);
}
}
return count;
}
}