【LeetCode刷题日记】哈希表:从0基础到实战全解析

0 阅读7分钟

🛩® 欢迎大佬莅临指导°˖✧\color{#FFD700}{ \textbf{🛩️ 欢迎大佬莅临指导°˖✧}}⚡⚡

🔥 个人主页:北极的代码(欢迎来访)
🎬 作者简介:java后端学习者
❄️ 个人专栏:LeetCode刷题日记.....持续更新中
命运的结局尽可永在,不屈的挑战却不可须臾或缺

🌿 前言\color{#D4AF37}{ \textbf{🌿 前言}}

我们已经完成了对链表章节算法的学习,关于这些方法思路的总结,我准备找一个专门的时间总结出来,以思维导图 为发散,将知识点串联起来,接下来我们继续学习哈希表及其相关的算法题目,覆盖基础知识到实战演练。

哈希表\color{#D4AF37}{ \textbf{哈希表}}

(英文名字为Hash table,国内也有一些算法书籍翻译 为散列表,大家看到这两个名称知道都是指hash table就可以了)。 我学习的是java,这里分享的就是在java中的哈希表,跟c语言或者其他的语言差别挺大的,java的哈希表是已经封装好了的,不需要自己去实现,直接拿来用就可以。

Java 哈希表(Hash Table)全基础讲解\color{#D4AF37}{ \textbf{Java 哈希表(Hash Table)全基础讲解}}

要刷力扣 哈希表题型,Java 里哈希表核心就是 2 个类:HashMap + HashSet,我把所有刷题必需的基础知识、用法、区别、技巧一次性讲全。

哈希表到底是什么\color{#D4AF37}{ \textbf{哈希表到底是什么}}

哈希表 = 数组 + 哈希函数(本质定义) 类比一下: 哈希表 = 快递柜系统数组 = 快递柜格子

哈希函数 = 按手机号分配格子(计算出哈希值) Java HashMap = 带挂袋和大箱子的快递柜

链表 = 格子放不下,挂个袋子串起来 红黑树 = 袋子太长,换成有序大箱子 ​

哈希函数又是什么:

把任意对象,算出一个 int 类型的哈希值,用来定位数组下标的公式。

hashCode() 方法

> public native int hashCode();

给每个对象返回一个 int 数字

同一个对象多次调用,结果应该一样 想让两个对象视为同一个 key,必须 equals 为 true 且 hashCode 相同

HashMap 自己的哈希函数(真正用的)

HashMap 不会直接用 key.hashCode(),它会再做一次扰动,让分布更均匀,减少冲突。

JDK 源码里的哈希函数:

static final int hash(Object key) { int h; // key 为 null 时哈希值固定为 0 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

h >>> 16:把哈希值右移 16 位

^:异或运算

目的:把高 16 位和低 16 位混合,让哈希值更散列,减少冲突。

最后一步:算出数组下标

有了哈希值,还要转成数组下标:

index = hash & (table.length - 1); AI写代码 等价于取模,但位运算更快。

补充:这里是怎么减少冲突的,通过一个例子理解

假设我们有两个字符串:

"Aa" "BB" 我们直接算它们的 hashCode 和 原始下标:

灾难发生了!如果没有扰动,"Aa" 和 "BB" 这两个完全不同的字符串,会被映射到同一个数组下标(下标 0)!它们会发生严重的哈希冲突,都挤在一个链表后面。

但扰动之后?扰动是把高位和低位异或,改变了低位信息,让它们落在不同位置,完美避开冲突。

总结:

无扰动计算:就是直接用 hashCode & (len-1)。

核心目的:

扰动不是为了改这次的结果,而是为了保证大量不同的字符串,低位信息也不同,从而最大限度避免哈希冲突。

所以,Java 必须加那个 hash() 扰动函数。

Java 哈希表两大核心类 \color{#D4AF37}{ \textbf{Java 哈希表两大核心类 }}

用途底层重复规则
HashMap<K,V>存键值对(统计次数、映射关系)数组 + 链表 + 红黑树key 不可重复,重复会覆盖
HashSet<E>只存值(去重、判断存在)基于 HashMap元素不可重复

HashMap 最全用法

HashMap 是力扣哈希题使用频率最高的工具,专门处理:

  • 统计字符 / 数字出现次数
  • 两数之和这类映射查找
  • 记录索引位置
  1. 导包

import java.util.HashMap;

  1. 创建对象

// 常见写法(key 和 value 可任意类型) HashMap<Integer, Integer> map = new HashMap<>(); HashMap<String, Integer> map = new HashMap<>();

  1. 核心方法(背会这 10 个足够刷所有哈希题)

`// 1. 添加/修改元素:key 不存在=添加,存在=覆盖 map.put(key, value);

// 2. 获取元素:根据 key 拿 value map.get(key);

// 3. 判断是否包含某个 key map.containsKey(key);

// 4. 判断是否包含某个 value(很少用) map.containsValue(value);

// 5. 删除元素 map.remove(key);

// 6. 获取键值对数量 map.size();

// 7. 清空 map.clear();

// 8. 判断是否为空 map.isEmpty();

// 9. 获取所有 key(遍历用) map.keySet();

// 10. 获取所有 value(遍历用) map.values();`

  1. 遍历 HashMap(刷题必背 2 种)

for (Integer key : map.keySet()) { int value = map.get(key); System.out.println(key + " : " + value); }

for (Map.Entry<Integer, Integer> entry : map.entrySet()) { int key = entry.getKey(); int value = entry.getValue(); }

 HashSet 最全用法

HashSet 用于:

  • 判断一个元素是否出现过
  • 去重
  • 找重复元素
  1. 导包

import java.util.HashSet; 2.创建对象 HashSet<Integer> set = new HashSet<>();

  1. 核心方法(6 个足够)

// 1. 添加元素,重复添加会自动失败 set.add(element);

// 2. 判断是否包含 set.contains(element);

// 3. 删除元素 set.remove(element);

// 4. 大小 set.size();

// 5. 清空 set.clear();

// 6. 是否为空 set.isEmpty();

  1. 遍历

for (Integer num : set) { // 遍历所有不重复元素 }

HashMap vs HashSet 怎么选

  • 要统计次数、存映射关系 → HashMap

  • 只要去重、判断是否存在 → HashSet

Java 哈希表高频面试 / 刷题知识点\color{#D4AF37}{ \textbf{Java 哈希表高频面试 / 刷题知识点}}

1. 哈希冲突是什么

  • 两个不同 key 算出同一个数组下标
  • Java 解决:链地址法(链表)+ 红黑树优化

1. 链地址法(最核心)

  • 哈希表底层是一个数组
  • 每个数组位置是一个链表头
  • 冲突的 key 就挂在这个链表后面

2. JDK 1.8 升级:链表太长 → 转红黑树

为了防止链表太长导致查询退化成 O (n),Java 做了优化:

链表长度 ≥ 8 并且 数组长度 ≥ 64→ 自动转换成 红黑树

树节点数量 ≤ 6→ 退化成链表

为什么这样

链表:短的时候快 红黑树:长的时候查询 O (log n),比 O (n) 快很多

三、除了链地址法,还有哪些解决冲突方式?(面试常问对比)

Java 只用链地址法,但你要知道别的方法:

开放寻址法冲突了就往后找空位置:线性探测、二次探测→ 比如 ThreadLocalMap 用这个

再哈希法冲突就换一个哈希函数再算一次

建立公共溢出区冲突的统一放另一个数组

HashMap 减少冲突的其他手段(高频)

1. 哈希扰动(让 hash 更散列)

Java 不会直接用 key.hashCode(),而是做了高低位异或扰动

static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 目的:让哈希值分布更均匀,减少冲突

2. 负载因子 loadFactor

默认 0.75

意思:数组满 75% 就扩容

  • 太大 → 冲突变多
  • 太小 → 空间浪费

3. 扩容机制

默认容量 16,扩容 ×2扩容后重新计算所有位置,打散冲突。

对比哈希扰动扩容机制
作用对象hash 值数组容量
发生时间每次 put 时满负载时
结果让 hash 更散数组变大 ×2
是否迁移数据不迁移必须迁移所有数据
目的减少冲突整体降低冲突频率
层级前端优化后端扩容

2. 为什么哈希表操作是 O (1)

  • 哈希函数直接定位下标,不遍历全表
  • 最坏 O (n),但力扣题目全部按平均 O (1) 算

3. 允许 null 吗

  • HashMap:key 允许 1 个 null,value 允许多个 null
  • HashSet:允许 1 个 null

4. 有序吗

  • 无序!  不保证插入顺序
  • 要有序用 LinkedHashMap(极少用)

为什么要重写 hashCode () 和 equals ()

面试必考:

  • 如果两个对象 equals 为 true,hashCode 必须相同
  • 否则它们会被当成不同 key,存到不同位置,HashMap 就错乱了

易混点:

实现类底层结构是否有序线程安全null 允许时间复杂度
HashMap数组 + 链表 + 红黑树无序1 个 null keyO(1)
Hashtable数组 + 链表无序不允许O(1)
LinkedHashMapHashMap + 双向链表插入有序允许O(1)
TreeMap红黑树按键排序key 不能为 nullO(log n)
  1. 结语:如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!

d395e732037dfe3017f13b484a8ecc3c.jpg