资源引用:
哈希表理论基础(代码随想录 (programmercarl.com))
leetcode题目:
242.有效的字母异位词(242. 有效的字母异位词 - 力扣(LeetCode))
242.有效的字母异位词(242. 有效的字母异位词 - 力扣(LeetCode))
202.快乐数(202. 快乐数 - 力扣(LeetCode))
1.两数之和(1. 两数之和 - 力扣(LeetCode))
例行碎碎念:
今天不够自律,这两天起的都有些晚,休息的不是很好,希望能够尽快调整,另外由于准备考试的原因也没能及时完成day7的练习,希望自己能够在后面提高效率,慢慢补上,不过今天做题还是很有手感的,想法很对哈哈哈,总之加油!
哈希表理论基础(代码随想录 (programmercarl.com))
哈希表
定义:
哈希表(Hash Table),又名散列表
性质:
是根据关键码的值而直接进行访问的数据结构
示例:
数组就是一张哈希表,关键码是数组的索引下标,即可直接访问
应用:
快速判断一个元素是否存在于集合中
优势:
查询的时间复杂度为O(1)
引子:
将元素值映射到哈希表涉及到hash function——哈希函数
哈希函数
示例:
以“查询学校是否有该姓名的学生”为例,
哈希函数把学生的姓名直接映射为哈希表上的索引,如下图所示:
通过hashCode将名字转化为数值

hashCode通过特定编码方式,将其他数据格式转化为不同的数值,从而将学生名字(字符串)映射成哈希表上的索引数字(整型)
引子:
hashCode值>tableSize,对该值取模,防止越界。
该方式存在问题,当学生总数大于哈希表的大小,存在多个学生映射到相同索引下标的情况,通过哈希碰撞来处理。
哈希碰撞:
定义:
不同的两个数据映射到同一下标位置,叫做哈希碰撞
(1)拉链法:tableSize<dataSize
通过链表存储冲突元素,关键是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

(2)线性探测法(开放地址法):tableSize>dataSize
产生冲突,则向下遍历直至找到一个空位
常见的三种哈希结构:
1.数组
2.集合(set):数值不重复、不可更改,增删不冲突
TreeSet的底层实现是红黑树,这是一种平衡搜索二叉树,所以key值有序的且不可更改,只能增删
HashSet的底层实现是哈希表,查询和增删效率是最优的,优先使用
| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
| TreeSet | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
| HashSet | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
3.映射(map):key值不重复、不可修改
(key, value)结构
TreeMap的底层实现是红黑树,这是一种平衡搜索二叉树,所以key值有序的且不可更改,只能增删
HashMap的底层实现是哈希表,查询和增删效率是最优的,优先使用
| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
| TreeMap | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
| HashMap | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
总结:
快速判断一个元素是否存在于集合中——哈希法
牺牲空间换取时间——哈希法
242.有效的字母异位词(242. 有效的字母异位词 - 力扣(LeetCode))
题目分析:
字母异位词的意思是对原词中的全部字母重新排序,并使用且仅使用每个字母一次。需要快速查询每种字母是否对应存在且个数相同,考虑使用简单的哈希表——数组进行解决。
解题思路:
已知s、t仅存在小写字母,故构建长度为26的数组record[26]记录每个字母的出现频率,下标值通过s[i]-'a'的ASCII值对应,注意初始化(java自动实现)。
首先判断两个字符串长度是否相同或是否存在空字符串,如果不相同则直接返回false
其次先对其中一个字符串进行字母频率统计,出现则记录数组中对应key的value++
然后再对另一个字符串进行字母频率统计,出现则record中对应key的value--
最后遍历检查record中是否存在非零项,若存在非零项,说明不为字母异位词;若不存在,则表明是。
反思:
1.在java中,获取String s中的字符,通过s.charAt(i)的方式获取
2.在java中,获取数组的长度使用array.length,获取字符串的长度使用string.length()
3.在java中,获取任意一种哈希表的长度,推荐使用 .size()方法
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length() == 0 && t.length() == 0){
return true;
}
if(s.length() != t.length() || s.length() == 0 || t.length() == 0){
return false;
}
int[] record = new int [26];
for(int i=0;i<s.length();i++){
record[s.charAt(i)-'a']++;
}
for(int j=0;j<t.length();j++){
record[t.charAt(j)-'a']--;
}
for(int k=0;k<record.length;k++){
if(record[k] != 0){
return false;
}
}
return true;
}
}
349.两个数组的交集(349. 两个数组的交集 - 力扣(LeetCode))
题目分析:
求两个给定数组的交集,输出结果中的每个元素是唯一的,顺序无需考虑,考虑使用HashSet实现。
解题思路:
将nums1映射到hashSet,查询num2中的每个元素是否包含在hashSet中,如果有该元素存在,则在结果hashSet中添加该值。最后将hashSet转换成数组进行输出。
注意检查nums1和nums2是否为空,若为空则直接返回new int[0]。
反思:
1.注意import java.util.*
2.for循环的遍历方式优化
//第一种遍历方式,j为指针
for(int j=0;j<nums2.length;j++){
if(set1.contains(j)){
resSet.add(j);
}
}
//第二种遍历方式,j直接为数组元素
for (int j : nums2) {
if (set1.contains(j)) {
resSet.add(j);
}
}
3.set和array的对比:set的占用空间大,速度慢,映射过程的hash计算需要耗时,在数据量大的情况,差距是很明显的。
import java.util.HashSet;
import java.util.Set;
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if(nums1.length == 0 || nums2.length == 0){
return new int[0];
}
Set<Integer> set1 = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
for(int i=0;i<nums1.length;i++){
set1.add(nums1[i]);
}
for(int j=0;j<nums2.length;j++){
if(set1.contains(nums2[j])){
resSet.add(nums2[j]);
}
}
//方法1:将结果集合转为数组
return resSet.stream().mapToInt(x -> x).toArray();
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
int[] arr = new int[resSet.size()];
int j = 0;
for(int i : resSet){
arr[j++] = i;
}
return arr;
}
}
202.快乐数(202. 快乐数 - 力扣(LeetCode))
题目分析:
快乐数n:该数满足经过有限次如下运算——每次运算将该数替换为它每一位数的平方和——最终结果为1。若不满足快乐数,则是无限循环但使用不为1,此处可以利用set判断其sum是否重复出现。
解题思路:
用一个set记录每一次重复该计算的得数sum,若第一次得出该数,则计入set,否则说明陷入循环,返回false。
若未陷入循环,则说明仍在有限次范围内,此时可以计算sum直至为1。(注意,值为1后也会陷入循环,必须注意判断)
关于取位方式,运用取余和除法。
总结:
- 返回值是boolean值,可以通过返回“判别式”来简化。
- 循环不变量条件要找准,①是否为1 ②是否陷入sum循环
- 注意第一个sum也要添入set
import java.util.*;
class Solution {
public boolean isHappy(int n) {
Set<Integer> sums = new HashSet<>();
int sum=n;
while(sum != 1 && !sums.contains(sum)){
sums.add(sum);
int res=sum,single=0,temp=0;
do{
single = res%10;
temp += single*single;
res = res/10;
}while(res > 0);
sum = temp;
}
return sum==1;
}
}
1.两数之和(1. 两数之和 - 力扣(LeetCode))
题目分析:
给定一个整数数组nums和目标值target,请你在nums中找出和为target的两个整数,并返回数组下标——考虑使用HashMap,键为数组的值,值为数组的下标。
解题思路:
首先处理边界条件,如果nums为空则直接返回长度为2的空数组res
然后建立HashMap,键值对存储数组的值及其对应下标
接着遍历当前元素,在map中寻找是否存在匹配的key,如果存在,取其value和该元素下标共同存入res数组并结束遍历;若不存在,则将该元素及其下标作为新的键值对存入map
最后返回res数组
总结思考:
- 为什么使用哈希表:需要根据元素值来查找是否存在匹配的项
- 为什么用map:既要存储元素值用于查找匹配,又要最终返回其下标,故采用(key,value)
- 本题中map是用来存什么的:遍历数组中的元素,在map中查找是否有匹配的key,若不存在,将该元素及其下标以(key,value)的形式存入map,用于后续数组中的其他元素进行查找匹配
- map中的key和value存什么:分别存数组中的元素值和它的下标
- 逐个遍历并逐个加入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> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i]; // 遍历当前元素,并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i); // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}
}

421

被折叠的 条评论
为什么被折叠?



