哈希表理论基础建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。
什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。
文章讲解:代码随想录
哈希表都是用来快速判断一个元素是否出现集合里。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
刷题遇到哈希,只用考虑数组(哈希值较小,范围较小可控)、set(数值很大,分布分散)、map(k对应value)。能用数组尽量用数组,比较快(因为set结构比数组复杂,数组映射最直接,运行速度也最快)
哈希函数,把数据直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道数据是否存在于哈希表。
哈希碰撞:多个数据都被映射到同一个索引下标的位置。两种解决方法:拉链法和线性探测法
拉链法: 将发生冲突的元素都存储在链表中。(数据规模是dataSize, 哈希表的大小为tableSize)
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
线性探测法:将后来的数据存入发生冲突位置的下一个位置。一定要保证tableSize大于dataSize。
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的。
在map 是一个key value 的数据结构,map中,对key是有限制(不可修改),对value没有限制的,因为key的存储方式使用红黑树实现的。java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储,不过给我们的使用方式,还是哈希法的使用方式,即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
242.有效的字母异位词建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。
题目链接/文章讲解/视频讲解: 代码随想录
两个字符串是不是由相同字符组成的字符串。
只有a~z,用一个大小为26的数组统计一个字符串中的字符出现情况,减去另一个字符串,如果结果为全零的数组,则是字母异位词。
class Solution {
public boolean isAnagram(String s, String t) {
int[] hash = new int[26];
// 长度
if(s.length() != t.length()) return false;
for(int i=0; i
349. 两个数组的交集建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。
题目链接/文章讲解/视频讲解: 代码随想录
需要去重
将nums1进行处理,转化为哈希表,然后遍历num2,看其中元素是否存在于该哈希表,若存在,则存入result集合。
unordered set映射操作、取值操作效率最高(并不需要对数据进行排序,而且还不要让数据重复)。
为什么不都用set,为什么还要用数组?
直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。这个耗时,在数据量大的情况,差距是很明显的。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if(nums1 == null || nums1.length ==0 || nums2 == null || nums2.length ==0){
return new int[0];
}
Set set1 = new HashSet<>();
Set result = new HashSet<>();
for(int temp: nums1){
set1.add(temp);
}
for(int temp: nums2){
if(set1.contains(temp)){
result.add(temp);
}
}
//使用流操作将集合转为数组
return result.stream().mapToInt(x -> x).toArray();
}
}
202. 快乐数建议:这道题目也是set的应用,其实和上一题差不多,就是 套在快乐数一个壳子
题目链接/文章讲解:代码随想录
题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
1. 两数之和
建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用map解决哈希问题的第一题。
建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。
题目链接/文章讲解/视频讲解:
代码随想录
为什么会想到用哈希表?判断元素是否出现过,或元素是否存在这个集合。
遍历一个元素时,判断跟它相加能组成target的元素之前是否遍历过。
将遍历过的元素加到一个集合中,然后每次遍历一个新的位置后,判断想要寻找的这个元素是否在这个集合中出现过。要存放两个元素,所以用map (元素,下标)。查找元素是否出现过,所以以元素为key
class Solution {
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int wanted = target - nums[i];
if(map.containsKey(wanted)){
return new int[]{map.get(wanted), i};
}else{
map.put(nums[i],i);
}
}
return new int[]{-1, -1};
}
}