什么时候用哈希表?
:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。
哈希表基础知识
在这里先讲下哈希表基础知识:我们都知道数组可以通过下标索引直接访问,比如array[0],时间复杂度是O(1),那么哈希表也是通过key-value对应的直接查询的表,本质数组也是哈希表的一种。
映射
举例:如何快速判断一个学生是否是 学算法大学 的呢?也就是key="张三同学",若value=“学算法大学” 就对了。那么就生成一个Hashtable,把value=“学算法大学“的学生名字映射为0.1...n这样的下标,这样就可以用学生的名字当作数组的下标,快速查下表对应的value值了。
怎么名字映射为数字下标呢?哈希函数:哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
哈希碰撞
但是如果两个名字都映射到一个索引呢?这就引入新问题:哈希碰撞
解决方式有两种:线性探测和拉链法:根据TableSize也就是哈希表索引长度是否能足够包含所有的名字映射(学生总数)。
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
例题
例题1:用数组做Hash题
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
说明: 你可以假设字符串只包含小写字母。
思路:采用的不是hash,但是却是hash映射的思路,我们把字母a-z映射为一个a[0..25]的数组,把输入的字符串都减去字节'a',获得的值就是从0-25的下标;比如输入的“asd”就存入数组中各个位置,再用输入的字符串t,剪一下就知道是否完全匹配,
代码细节:从字符串中取每个字符用s.charAt();数组int[26]才对,长度是26,下标是0-25
class Solution {
public boolean isAnagram(String s, String t) {
int[] a = new int[26];
for(int i = 0;i<s.length();i++){
a[s.charAt(i)-'a']++;
}
for(int i = 0;i<t.length();i++){
a[t.charAt(i)-'a']--;
}
for(int count:a){
if(count!=0)
{
return false;
}
}
return true;
}
}
遇到哈希问题我直接都用set不就得了,用什么数组啊。
直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
例题二:用set
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2
这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
哈希表擅长解决:给你个元素判断是否出现过。
数值很大数组没办法,用set
思路:
把nums1转化为哈希表,用nums2查,结果放到result中。
哈希表中存储的数据是通过hashcode计算后得到一个数字比如1179395,然后存储进去。再来数据比较还是通过hashcode计算后比较。而且不能存相同的数据,因为计算出来的数字是一样的。
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<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
for(int i:nums1){
set1.add(i);
}
for(int i:nums2){
if(set1.contains(i)){
set2.add(i);
}
}
int[] ans = new int[set2.size()];
int j=0;
for(int i:set2){
ans[j++]=i;
}
return ans;
}
}
例题三:循环有重复的时候跳出使用hashset.contains
思路.对n逐位平方和形成新数字存入hash中,判断不是1且未出现过,若是就继续循环平方和。
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while(n!=1&&!set.contains(n)){
set.add(n);
n = kuaileshu(n);
}
if(n == 1){
return true;
}else{
return false;
}
}
private int kuaileshu(int n){
int temp=0;
while(n>0){
temp += (n%10)*(n%10);
n=n/10;
}
return temp;
}
}
例题4:用HashMap做题Key-value
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
- 本题map是用来存什么的?是用来存放遍历过的元素和下标,target-cur然后查找出现过?
代码:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums.length==0||nums==null){
return res;
}
Map<Integer,Integer> map = new HashMap<>();
int i=0;
for(;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
res[0] = i;
res[1] = map.get(target-nums[i]);
break;
}else{
map.put(nums[i],i);
}
}
return res;
}
}