哈希专题的本质:
把"查找问题"转换成"空间存储问题"
文章目录
- 题目解析
- 知识点总结
-
-
- List(有序,可重复)
- Set(无序,不可重复)
- [Map(键值对,Key 不可重复)](#Map(键值对,Key 不可重复))
- 一张图总结
- 怎么选(速查)
- 一句话记忆
-
题目解析
1. 两数之和
暴力
java
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0;i < nums.length - 1;i++){
for(int j = i + 1;j < nums.length;j++){
if(nums[i] + nums[j] == target){
return new int[]{i,j};
}
}
}
return new int[0];
}
}
哈希表法
- 在遍历同时记录一些信息,用来省去一层循环,以"空间换时间"
- 需要记录已经遍历过的数值和它所对应的下标,可以查找表实现
- 查找表的带两个常用实现:哈希表和平衡二叉搜索树
法一
java
class Solution {
public int[] twoSum(int[] nums, int target) {
int len = nums.length;
Map<Integer,Integer> hm = new HashMap<Integer,Integer>();
for(int i=0;i<len;i++){
if(hm.containsKey(target - nums[i])){
return new int[]{hm.get(target - nums[i]),i};
}
hm.put(nums[i],i);
}
return new int[0];
}
}
法二
先创建一个哈希表
java
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer,Integer> hm = new HashMap<Integer,Integer>();
for(int i = 0;i < nums.length;i++){
hm.put(nums[i],i);
}
for(int j = 0;j < nums.length;j++){
Integer diff = target - nums[j];
if(hm.containsKey(diff) && hm.get(diff) != j){
res[0] = j;
res[1] = hm.get(diff);
return res;
}
}
return res;
}
}
49. 字母异位词分组
排序
关键:所有异位词排序后都会得到相同的字符串

java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String,List<String>>();
for (String str : strs) {
char[] array = str.toCharArray();//String不能排序,转换成字符数组再排序
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());
//从map中获取key对应的值;如果key不存在,就返回一个"默认值"------这里是一个新的空ArrayList<String>;如果key已存在,返回对应的List<String>
list.add(str);
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
}
128. 最长连续序列
哈希
每个数都判断一次这个数是不是连续序列的开头那个数
- 用哈希表查找这个数前面一个数是否存在,即num-1在序列中是否存在。存在那这个数肯定不是开头,直接跳过。
- 因此只需要对每个开头的数进行循环,直到这个序列不再连续,因此复杂度是O(n)
java
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>();
for(int num : nums){
num_set.add(num);
}
int ans = 0;
for(int num : num_set){
if(!num_set.contains(num - 1)){ //判断这个数是否是开头 是则继续
int temp_num = num;
int temp_long = 1;
while(num_set.contains(temp_num + 1)){
temp_num += 1;
temp_long += 1;
}//统计由这个数开头的最长序列
ans = Math.max(ans,temp_long);
}
}
return ans;
}
}
知识点总结
List(有序,可重复)
ArrayList
- 底层:数组
- 特点:查询快(O(1)),增删慢(中间插入/删除要移动元素,O(n))
- 适用场景 :读多写少,比如存一组配置、查列表
LinkedList
- 底层:双向链表
- 特点:增删快(O(1)),查询慢(要遍历,O(n))
- 适用场景 :频繁在头尾增删,比如队列、消息队列
实际开发中 90% 的情况用 ArrayList,LinkedList 很少用。
Set(无序,不可重复)
HashSet
- 底层:HashMap
- 特点:无序,增删查都是 O(1)
- 适用场景 :去重 + 快速判断元素是否存在,比如判断用户是否已登录、判断数字是否出现过(你刚才那道题就是)
LinkedHashSet
- 底层:LinkedHashMap
- 特点 :保持插入顺序,其余和 HashSet 一样
- 适用场景:去重 + 需要保持插入顺序
TreeSet
- 底层:红黑树
- 特点 :自动排序,O(log n)
- 适用场景 :需要去重 + 排序,比如排行榜、Top K
Map(键值对,Key 不可重复)
HashMap
- 底层:数组 + 链表 + 红黑树
- 特点:无序,增删查 O(1),允许 null key 和 null value
- 适用场景 :最常用的 Map,缓存、分组、计数等,比如你刚才那道题用 HashMap 分组
LinkedHashMap
- 底层:LinkedHashMap
- 特点 :保持插入顺序(或访问顺序),其余和 HashMap 一样
- 适用场景:需要保持顺序的键值对,比如 LRU 缓存
TreeMap
- 底层:红黑树
- 特点 :按 Key 自动排序,O(log n)
- 适用场景 :需要按 Key 排序,比如按时间排序的事件、区间查询
一张图总结
| 集合 | 底层 | 有序 | 可重复 | 线程安全 | 典型场景 |
|---|---|---|---|---|---|
| ArrayList | 数组 | ✅ 插入顺序 | ✅ | ❌ | 列表展示、读多写少 |
| LinkedList | 双向链表 | ✅ 插入顺序 | ✅ | ❌ | 队列、频繁增删 |
| HashSet | HashMap | ❌ | ❌ | ❌ | 去重、快速查找 |
| LinkedHashSet | LinkedHashMap | ✅ 插入顺序 | ❌ | ❌ | 去重 + 保序 |
| TreeSet | 红黑树 | ✅ 排序 | ❌ | ❌ | 去重 + 排序 |
| HashMap | 数组+链表+红黑树 | ❌ | Key ❌ | ❌ | 最常用,分组/缓存/计数 |
| LinkedHashMap | 同上+双向链表 | ✅ 插入顺序 | Key ❌ | ❌ | 保序键值对、LRU |
| TreeMap | 红黑树 | ✅ 按Key排序 | Key ❌ | ❌ | 按Key排序、区间查询 |
怎么选(速查)
| 需求 | 用什么 |
|---|---|
| 只是存一组数据,按顺序遍历 | ArrayList |
| 去重 | HashSet |
| 去重 + 排序 | TreeSet |
| 去重 + 保持插入顺序 | LinkedHashSet |
| 键值对,最常用 | HashMap |
| 键值对 + 按 Key 排序 | TreeMap |
| 键值对 + 保持插入顺序 | LinkedHashMap |
| 多线程环境 | ConcurrentHashMap(替代 HashMap) |
一句话记忆
List 管顺序,Set 管去重,Map 管映射。默认都用 Hash 系列,需要排序换 Tree,需要保序换 Linked。
接下来一个月会持续更新算法题和八股面试题,欢迎大家一起学习进步

