目录
哈希表理论基础
文章链接:代码随想录_哈希表理论基础
定义:哈希表是根据关键码的值而直接进行访问的数据结构。(简单来说就是一个数组,数组的索引下标就是所说的关键码)
- 那么哈希表能解决什么问题呢?一般哈希表都是用来快速判断一个元素是否出现集合里。如果是正常的数组和链表,如果要找到目标元素,要从头到尾一一遍历。
- 但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
哈希函数:一种映射
哈希碰撞:多个内容映射到一个hashCode的情况;有两种解决方法:拉链法、线性探测法。
- 拉链法:冲突元素全部被储存
- 线性探测法:使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
常见的三种哈希结构:
- 数组
- set (集合)
- map(映射)
Leetcode 242 有效的字母异位词
文档讲解:代码随想录_有效的字母异位词
视频讲解:b站讲解
数组也是哈希表的实现方式之一。使用数组来做哈希的题目,是因为题目都限制了数值的大小。
第一想法:遍历两个字符串,创建两个数组,各自统计每个字符串中各个字符出现的次数,然后比较两个字符串的个字符出现的次数(即两个数组的值)是否相同。
class Solution {
public boolean isAnagram(String s, String t) {
int[] record1 = new int[26];
int[] record2 = new int[26];
for(int i=0;i<s.length();i++){
//charAt(i):输出第i个位置字符的值,输出的是字符
record1[s.charAt(i)-'a']++;
}
for(int i=0;i<t.length();i++){
//charAt(i):输出第i个位置字符的值,输出的是字符
record2[t.charAt(i)-'a']++;
}
for(int i=0;i<26;i++){
if(record1[i]!=record2[i]){
return false;
}
}
return true;
}
}
学习记录:
可以只使用一个数组,遍历第一个字符串的时候,数组值+1;遍历第二个字符串的时候,数组对应位置的值-1;最后判断数组值的总和是否为0。
class Solution {
public boolean isAnagram(String s, String t) {
int[] record = new int[26];
for(int i=0;i<s.length();i++){
//charAt(i):输出第i个位置字符的值,输出的是字符
record[s.charAt(i)-'a']++;
}
for(int i=0;i<t.length();i++){
//charAt(i):输出第i个位置字符的值,输出的是字符
record[t.charAt(i)-'a']--;
}
for(int i=0;i<26;i++){
if(record[i]!=0){
return false;
}
}
return true;
}
}
Leetcode 349 两个数组的交集
题目链接:Leetcode_349_两个数组的交集
文档讲解:代码随想录_两个数组的交集
视频讲解:b站讲解
第一想法:没什么想法
学习记录:
题目虽然限制了数值范围,但是哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。所以此处使用set来解决此问题。
整体思路就是将第一个数组建立成一个哈希表,然后遍历第二个数组的内容进行比较,然后返回二者去重后的交集。
JAVA中HashSet的使用
使用集合前,需要用import语句导入Set接口,以及它的实现类HashSet
import java.util.Set; // 导入Set
import java.util.HashSet; // 导入HashSet
// 创建一个HashSet实例
Set<String/Integer> set = new HashSet<>();
set结构还提供了一些常见方法方便使用:
- add(element):向集合中添加元素,如果元素已存在,则不会重复添加。
- remove(element):从集合中移除指定元素。
- isEmpty():判断集合是否为空。
- size():返回集合中的元素数量。
- clear():清空集合中的所有元素。
- contains():用于检查是否有任何键映射到给定值元素(val_ele)中。
写法一:HashSet实现
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> reset = new HashSet<>();
for(int i:nums1){
set1.add(i);
}
for(int i:nums2){
if(set1.contains(i)){
reset.add(i);
}
}
int[] rearr = new int[reset.size()];
int j=0;
for(int i:reset){
rearr[j]=i;
j++;
}
return rearr;
}
}
写法二:数组实现
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if(nums1==null || nums1.length==0 || nums2==null || nums2.length==0){
return new int[0];
}
int[] arr1 = new int[1002];
int[] arr2 = new int[1002];
for(int i:nums1){
arr1[i]++;
}
for(int i:nums2){
arr2[i]++;
}
int count = 0;
for(int i=0; i<1002; i++){
if(arr1[i]>0 && arr2[i]>0){
count++;
}
}
int[] resarr = new int[count];
int j = 0;
for(int i=0; i<1002; i++){
if(arr1[i]>0 && arr2[i]>0){
resarr[j]=i;
j++;
}
}
return resarr;
}
}
Leetcode 202 快乐数
题目链接:Leetcode_202_快乐数
文档讲解:代码随想录_快乐数
第一想法:用哈希表记录每一次计算的结果,判断这次的结果在以往有没有重复出现过,出现过的话就会进入循环,输出false,计算到1则输出true;但是我遇到了一个问题,n很大的情况下,怎么取出每一位数字,计算其每位数字的平方之和。
学习记录:
写一个专门的子函数用来计算每位数字的平方和,用一个循环先进行mod10的操作得到当前个位数字,然后整除10,去掉当前个位数字,不断执行上述操作,直到n=0。
class Solution {
public boolean isHappy(int n) {
Set<Integer> set1 = new HashSet<>();
while(n != 1 && set1.contains(n)==false){
set1.add(n);
n = getn(n);
}
if(set1.contains(n)){
return false;
}
return true;
}
private int getn(int n){
int res = 0;
while(n>0){
int a = n % 10; //取模操作得到个位数
res = res + a * a;
n = n/10; //除以10去掉当前的个位
}
return res;
}
}
Leetcode 1 两数之和
题目链接:Leetcode_1_两数之和
文档讲解:代码随想录_两数之和
视频讲解:b站讲解
第一想法:首先取第一个小于目标值的数字,然后计算差值,判断差值是否在哈希表中,如果存在就找到两外一个数的下标,不存在则取下一个小于目标值的数字,重复上述操作。但如何找下标呢?循环遍历感觉有点复杂。
学习记录:
本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。本题用map形式的构造方式。
JAVA中HashMap的使用
使用HashMap前需要导入对应的包文件
import java.util.Map; // 引入 Map 接口
import java.util.HashMap; // 引入 HashMap 实现类
Map<String, Integer> studentScores = new HashMap<>();
map结构还提供了一些常见方法方便使用:
- put(key,value):将指定的键和值添加到map中,如果键已存在,则替换对应的值。
- get(key):根据key获取对应的值value。
- remove(key):根据key移除对应的键值对。
- containsKey(key):检查map中是否包含key键。
- containsValue(value):检查map中是否包含value值。
- keySet():返回包含所有键的集合。
- values():返回包含所有值的集合。
- entrySet():返回包含所有键值对的集合。
解题思路:
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。(用此种方法求解可以少遍历一次,比我之前的想法快)
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map<Integer,Integer> map1 = new HashMap<>();
for(int i=0; i<nums.length; i++){
int temp = target-nums[i];
if(map1.containsKey(temp)){
res[0] = i;
res[1] = map1.get(temp);
break;
}
map1.put(nums[i],i);
}
return res;
}
}
小结
小结一下三种常见哈希结构的使用情况:
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,如果需要直到元素的下标就不合适。
- map可以判断元素有没有遍历,又可以找到元素对应的下标。