哈希表理论基础
哈希表其实就是一个数组
哈希表的key看作数组的索引下标,通过索引下标访问数组中的值。
那么哈希表能够解决什么问题呢?
哈希表主要解决的是:快速判断某个元素是否出现在集合中
例如要查询某个名字是否出现在学校里,我们遍历的话时间复杂度是O(n),用哈希表的话时间复杂度就是O(1)。
我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
将学生的名字映射到哈希表就会使用到hash function,也就是哈希函数
哈希函数
我们将学生的名字通过哈希函数计算出哈希表中的索引下标,需要查找的时候直接通过索引下标查看是否有该学生名字即可
当元素数量大于tableSize怎么办呢?我们这里的计算做了一个取模的操作,保证了计算出来的下标都是在tableSize内。
但是会计算出两个不同名字有相同索引下标的情况,这就是哈希碰撞
哈希碰撞
一般哈希碰撞有两种解决办法:拉链法和线性探测法。
拉链法
刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了
线性探测法
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
常见的三种数据结构
同样是用于哈希表,用于快速判断元素是否出现在集合中,不过各自有不同特点。
- 数组:一般用于数值较小的元素,避免浪费空间。
- Set:一般用于数值较大的元素,且自动做去重操作。
- Map:一般用于需要存放key和value的情况。
242.有效的字母异位词
思路:我们需要判断字符是否出现在另一个字符串中,就首先想到使用哈希法。然后选择数据结构,这道题只包含小写字母,就是只有26个字符,数值较小,所以可以使用数组来实现哈希表。第一个for循环,将字符串s存入数组中,在对应下标++;第二个for循环,遍历字符串t,在对应下标做–操作。最后遍历数组,如果所有值为0,则说明两个字符串为有效的字母异位词。
class Solution {
public static boolean isAnagram(String s, String t) {
int[] record = new int[26];
for (int i=0;i<s.length();i++){
record[s.charAt(i) - 'a']++;
}
for (int i=0;i<t.length();i++){
record[t.charAt(i) - 'a']--;
}
for (int count:record){
if (count != 0){
return false;
}
}
return true;
}
}
349. 两个数组的交集
思路:同样是判断一个数组的元素是否在另一个数组中存在,所以使用哈希法。这里我们选择使用Set,因为有相同元素只需要返回一个,所以得做去重操作。先将数组nums1存入set,再遍历nums2比较每个元素是否在set中,包含的话就放入resSet里,最后返回resSet。
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> resSet = new HashSet();
for (int i : nums1){
set1.add(i);
}
for (int i : nums2){
if (set1.contains(i)){
resSet.add(i);
}
}
return resSet.stream().mapToInt(x -> x).toArray();
}
}
202. 快乐数
思路:本题需要存放sum,不断循环,判断新计算出的sum是否在集合中出现过,所以用哈希法。当n=1 或者出现set中包含n的时候,就可退出循环。还有就是各个位数的数字平方相乘的操作,写在nextNumber()方法里了,通过取模和除法拿到每个数字从而平方相乘。
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet();
while (n!=1 && !set.contains(n)){
set.add(n);
n = nextNumber(n);
}
return n == 1;
}
public int nextNumber(int n){
int res = 0;
while (n > 0){
int temp=0;
temp = n % 10;
res += temp*temp;
n = n/10;
}
return res;
}
}
1.两数之和
思路:我们得判断需要的数字(比如target=9,元素=2,需要的数字就是9-2=7)是否在集合中,所以使用哈希法。因为需要存放数值和数组下标,所以采用map实现哈希表,key存数值,value存数组下标。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
Map<Integer,Integer> map = new HashMap();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])){
result[1] = i;
result[0] = map.get(target - nums[i]);
}
map.put(nums[i], i);
}
return result;
}
}