一、哈希表理论基础
哈希表(Hash table)是根据键(Key)而直接访问内存存储位置的数据结构。它通过一个哈希函数(Hash Function)将键映射到一个存储桶(Bucket)或槽(Slot)中。哈希函数的作用是把任意长度的输入(键)通过计算转换为固定长度的输出,这个输出值就是哈希值,哈希值对应着哈希表中的一个位置。
理想情况:
在理想情况下,哈希函数能够将不同的键均匀地映射到哈希表的各个位置,即不会出现多个键映射到同一个位置的情况(这种情况称为哈希冲突)。此时,无论哈希表中有多少个元素,我们只需要通过哈希函数计算一次哈希值,然后直接访问对应的位置,这个过程的时间复杂度是常数级别的,即 o(1)。
哈希冲突:
在实际应用中,哈希冲突是不可避免的,即不同的键可能会映射到同一个哈希值。为了解决哈希冲突,通常采用链地址法(Separate Chaining)或开放地址法(Open Addressing)等方法。当发生哈希冲突时,查找元素的时间复杂度可能会增加。例如,使用链地址法时,每个存储桶可能会存储一个链表或其他数据结构,当多个键映射到同一个存储桶时,需要在链表中进行线性查找,此时最坏情况下的时间复杂度会退化为 o(n),其中 n 是链表的长度。但如果哈希函数设计得好,哈希冲突的概率会比较小,平均情况下的时间复杂度仍然接近 o(1) 。
HashMap
HashMap 是 Java 集合框架中的一个类,它实现了 Map 接口,用于存储键值对(Key-Value)。HashMap 不保证元素的顺序,特别是不保证顺序恒久不变,允许使用 null 键和 null 值,但键必须是唯一的。
数据结构:Java 中的 HashMap 主要基于哈希表实现,在 JDK 1.8 及以后的版本中,采用了数组 + 链表 + 红黑树的数据结构。
- 数组:作为哈希表的基础结构,每个数组元素称为一个桶(Bucket),用于存储键值对节点。
- 链表:当发生哈希冲突(不同的键计算出相同的哈希值)时,新的键值对会以链表的形式存储在对应的桶中。
- 红黑树:当链表的长度超过一定阈值(默认为 8),并且数组长度大于 64 时,链表会转换为红黑树,以提高查找、插入和删除操作的性能。
TreeMap
数据结构:TreeMap 基于红黑树(Red - Black Tree)这种自平衡的二叉搜索树实现。红黑树是一种特殊的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色),通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。这种特性使得 TreeMap 在插入、删除和查找操作上能保证 的时间复杂度。
按键排序:TreeMap 会根据键的自然顺序或者创建 TreeMap 时提供的比较器(Comparator)来对键进行排序。如果键实现了 Comparable 接口,TreeMap 会使用其自然顺序;如果传入了自定义的 Comparator,则使用该比较器来决定键的顺序。
不允许键为 null:由于 TreeMap 要对键进行排序,而 null 无法参与比较,所以 TreeMap 不允许键为 null,但值可以为 null。
二、242.有效的字母异位词
题目链接/文章讲解/视频讲解: 代码随想录
思路:创建26大小的数组,遍历字符串 s 的每一位的ASCII码。因为都是小写字母,所以是连续的,通过减去“a”的ASCII码,即可将a-z字符映射到0-25。遍历得到 s 中的各字母数量,再遍历 t 字符串,减去数组中的字符数量,判断最后数组是否均为 0,若为0则是字母异位词。
class Solution {
public 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 j = 0; j<t.length(); j++){
record[t.charAt(j) - 'a']--;
}
for(int count:record){
if(count != 0){
return false;
}
}
return true;
}
}
时间复杂度 o(m+n)
三、349. 两个数组的交集
利用HashSet数据类型,Set代表无序不可重复集合,只能根据元素本身来访问。
将nums1的元素装入一个HashSet集合中,判断nums2中的元素是否存在(contains函数)于该数组中,并将重复的元素装进一个新的HashSet集合(不重复性),输出时转化为数组类型。
import java.util.HashSet;
import java.util.Set;
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);
}
}
int[] result=new int[resSet.size()];
int j = 0;
for(int i:resSet){
result[j++]=i;
}
return result;
}
}
四、202. 快乐数
题目链接/文章讲解:代码随想录
根据快乐数的定义,结果会得到1。若不是快乐数则会进入循环,那就会出现重复的数,还是用到哈希表用于快速查询一个元素是否出现在集合里。
记忆遍历整数每一位的方式。
class Solution {
public boolean isHappy(int n) {
//存储每次计算结果
Set<Integer> sums = new HashSet<>();
//没得到1就一直循环
while( !sums.contains(1) ){
int sum = 0;
while(n>0){
int a = n % 10;
sum += a*a;
n /= 10;
}
//出现重复则返回false
if(sums.contains(sum)){
return false;
}
sums.add(sum);
n = sum;
}
return true;
}
}
五、1. 两数之和
题目链接/文章讲解/视频讲解:代码随想录
注意点:
- 1、将数组装入Map集合时,应该以数值为key,索引为value,因为最后输出的是索引下标,要通过键来取值。
- 2、要先判断后新增元素,防止一个元素重复使用。
- 3、记忆在输出位置新增数组的格式。
class Solution {
public int[] twoSum(int[] nums, int target) {
if(nums == null || nums.length == 0){
return null;
}
Map<Integer,Integer> numsMap = new HashMap<>();
for(int i = 0 ; i<nums.length ; i++){
int a = target - nums[i];
if(numsMap.containsKey(a)){
a = numsMap.get(a);
return new int[] {a,i};
}
numsMap.put(nums[i],i);
}
return null;
}
}
1500

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



