一、哈希表介绍
哈希表法有三大类:数组,set,map。
1.数组
适用于存储密集的分布
2.set
HashSet:
特点:
基于 HashMap 实现。
使用哈希表存储元素。
允许 null 元素。
不保证顺序,插入顺序和迭代顺序可能不同。
线程不安全。增查效率:
插入效率:平均 O(1),最坏情况下 O(n)(当哈希冲突严重时)。
查找效率:平均 O(1),最坏情况下 O(n)(当哈希冲突严重时)。底层实现:基于 HashMap 实现,内部使用哈希表来存储元素。哈希表通过散列函数将元素的键值映射到表中的位置,以实现快速的插入和查找操作。
TreeSet:
特点:
基于 TreeMap 实现。
使用红黑树(自平衡二叉搜索树)存储元素。
不允许 null 元素。
保持元素的自然顺序或通过 Comparator 指定的顺序。
线程不安全。增查效率:
插入效率:O(log n),因为底层是红黑树结构,每次插入需要保持树的平衡。
查找效率:O(log n),因为红黑树的查找效率是对数级别。底层实现:基于 TreeMap 实现,内部使用红黑树(自平衡二叉搜索树)来存储元素。红黑树在插入和查找操作中保持平衡,以确保操作的时间复杂度为 O(log n)。
LinkedHashSet:
特点:
基于 HashSet 实现。
使用 LinkedHashMap 存储元素,保持插入顺序。
允许 null 元素。
保持元素插入顺序。
线程不安全。增查效率:
插入效率:平均 O(1),与 HashSet 类似,因为底层也是基于哈希表实现的。
查找效率:平均 O(1),与 HashSet 类似。底层实现:基于 HashSet 实现,并且通过双向链表维护插入顺序。内部使用 LinkedHashMap 来存储元素,除了使用哈希表来存储数据外,还维护了一个链表以记录插入顺序。
常用方法:
set.add("apple"); //添加元素
set.contains("apple"); //查询是否包含元素
set.remove("apple"); //移除元素
set.size(); //获得集合的大小
set.isEmpty(); //查询集合是否为空
set.clear(); //清除集合
3.Map
HashMap
特点:
基于哈希表实现,允许 null 键和值。
无序存储,不保证顺序。
线程不安全。
增查效率:
插入效率:平均 O(1),最坏情况 O(n)(当哈希冲突严重时)。
查找效率:平均 O(1),最坏情况 O(n)(当哈希冲突严重时)。
底层实现:
使用数组和链表(Java 8 以后使用红黑树)存储数据。
通过散列函数将键值映射到数组的位置
TreeMap
特点:
基于红黑树(自平衡二叉搜索树)实现。
有序存储,键按自然顺序或 Comparator 排序。
不允许 null 键。
线程不安全。
增查效率:
插入效率:O(log n),因为每次插入需要保持树的平衡。
查找效率:O(log n),因为红黑树的查找效率是对数级别。
底层实现:
使用红黑树存储数据。
通过比较器或键的自然顺序来维护顺序。
LinkedHashMap
特点:
基于哈希表和双向链表实现。
有序存储,按插入顺序或访问顺序。
允许 null 键和值。
线程不安全。
增查效率:
插入效率:平均 O(1),与 HashMap 类似。
查找效率:平均 O(1),与 HashMap 类似。
底层实现:
使用哈希表存储数据,同时维护一个双向链表以记录插入或访问顺序。
ConcurrentHashMap
特点:
线程安全的哈希表,适用于高并发场景。
不允许 null 键和值。
增查效率:
插入效率:O(1),分段锁机制使其在高并发下性能优越。
查找效率:O(1),与 HashMap 类似。
底层实现:
使用分段锁和哈希表存储数据,保证线程安全。
每个分段包含一个独立的哈希表和锁,以减少锁竞争。
Hashtable
特点:
线程安全的哈希表,老版实现类,不推荐使用。
不允许 null 键和值。
增查效率:
插入效率:O(1),但由于使用全表锁,性能较低。
查找效率:O(1),但由于使用全表锁,性能较低。
底层实现:
使用哈希表存储数据,所有方法都同步,保证线程安全。
每次操作都会锁住整个哈希表,性能较差。
常用方法:
map.put("one", 1); //增加one->1的键值对
map.get("one"); //获取key为one键值对的value
map.containsKey("one"); //查询key是否包含one
map.containsValue(1); //查询value是否包含1
map.remove("one"); //移除key为one的键值对
map.size(); //获取map的大小
map.isEmpty(); //查询map是否为空
map.clear(); //清除map表
二、242.有效的字母异位词
题目:给定两个字符串
s
和t
,编写一个函数来判断t
是否是s
的字母异位词。注意:若
s
和t
中每个字符出现的次数都相同,则称s
和t
互为字母异位词。学习链接:有效的字母异位词
状态:做出来了
时间复杂度:O(n) 空间复杂度:O(n)
细节之处:1
思路:由于元素分布密集,直接开一个26位的数组record,里面是a-z的映射,record记录s中各类小写字母出现的次数,然后再遍历t,在相应位置减去,因为如果俩个是字母异位词,record26都会为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']++; //1 因为是存储小写字母,直接线性存储就行
}
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. 两个数组的交集
题目:给定两个数组
nums1
和nums2
,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。学习链接:两个数组的交集
状态:做出来了
时间复杂度:O(n) 空间复杂度:O(n)
细节之处(知识点):1
思路:由于元素分布疏散,利用set1记录nums1不重复元素的集合 ,resset记录相交的元素,然后遍历nums2,判断set1是否包含该元素,包含则加入到resset里面。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
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[] res=resSet.stream().mapToInt(n->n).toArray(); //1 用stream流进行转换int,然后转换成数组
return res;
}
}
四、202. 快乐数
题目:编写一个算法来判断一个数
n
是不是快乐数。「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
学习链接:快乐数
状态:做出来了
时间复杂度:O(n) 空间复杂度:O(n)
细节之处:1 2 3
思路: 因为元素分布疏散,所以使用set,用set记录上一次替换的,如果有重复的替换,则一定是无限循环,返回false,若是res==1,则返回true.
class Solution {
public boolean isHappy(int n) {
HashSet<Integer> record = new HashSet<>();
int cur = n;
int res = 0;
if (n == 1) return true; // 1 下面代码在n=1的时候会判false,除非下面俩个if调换顺序,这里直接特判
while (true) {
record.add(cur);
while (cur > 0) {
res += (cur % 10) * (cur % 10);
cur = cur / 10;
}
cur = res; //2 更新 cur,res回到之前的状态,避免下次计算累加
res = 0;
if (record.contains(cur)) {
return false; //如果有重复的res,证明一定是循环,直接返回false
}
if (cur == 1) return true;
}
}
}
五、1. 两数之和
题目:给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
学习链接:两数之和
状态:做出来了
时间复杂度:O(n) 空间复杂度:O(n)
细节之处:1
思路:因为这道题不仅得存储值还得存储下标,所以需要使用到键值对,使用map,利用map记录值和下标,遍历一个数组,然后在之前的map里面寻找是否能够完成数学等式的元素,如果有,再判断是否是相同的元素,如果不是,则记录下来,返回结果。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
int count=0;
int[] res=new int[2];
for(int i:nums){
map.put(i,count++);
}
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])&&map.get(target-nums[i])!=i){ //1 判断map是否包含相加所需的元素,且这个元素不是相同的元素
res[0]=i;
res[1]=map.get(target-nums[i]);
break;
}
}
return res;
}
}
六、总结
元素分布密集使用数组,分散使用set,如果是键值对使用map,然后根据题目要求选择对应的实现类,若无特别要求,则使用hashset或者LinkedHashSet(增查效率高),map则使用hashMap或LinkedHashMap(增差效率高)
如果对大家有帮助,大家点个赞,收藏一下吧,文章如果有错误的,欢迎大家在评论区指出我的错误,我一定会及时改正的