哈希表有哪些算法?

一 概述

梳理哈希表相关的算法,按照基础、中级、高级进行分类,并提供了核心思想和算法描述。

二 基础算法

这类问题直接运用哈希表的基础特性(快速查找、去重、频率统计),通常只需一次或两次遍历即可解决。

1 两数之和

(1)核心思想:利用哈希表的O(1)查找特性,将"查找目标元素"转化为"验证互补元素是否存在"。

(2) 算法描述

a 初始化一个空哈希表,用于存储 {数组元素: 对应索引}。

b 遍历数组中的每一个元素 num 及其索引 i。

c 计算目标值 target 与 num 的差值 complement。

d 检查 complement 是否存在于哈希表中。

如果存在,则返回当前索引 i 和 complement 在哈希表中对应的索引。

如果不存在,则将当前 {num: i} 存入哈希表,继续遍历。

2 第一个只出现一次的字符

(1) 核心思想:使用哈希表进行频率统计,并通过二次遍历找到第一个满足条件的元素。

(2) 算法描述

a 初始化一个空哈希表,用于存储 {字符: 出现次数}。

b 第一次遍历字符串,对每个字符进行计数。

c 第二次遍历字符串,对于每个字符,检查其在哈希表中的计数。

d 返回第一个计数为1的字符。

3 两个数组的交集

(1) 核心思想:利用哈希集合(Set)的自动去重特性,进行高效的成员检查。

(2) 算法描述

a 将一个数组的所有元素放入一个哈希集合 set1,实现自动去重。

b 初始化一个结果集合 resultSet。

c 遍历第二个数组,检查每个元素是否存在于 set1 中。

d 如果存在,则将该元素加入 resultSet。

e 将 resultSet 转换为列表输出。

4 有效的字母异位词

(1) 核心思想:使用哈希表(或定长数组)统计字符频率,通过频率变化判断是否为异位词。

(2) 算法描述

a 如果两个字符串长度不等,直接返回 false。

b 初始化一个长度为26的数组(或哈希表),模拟从 'a' 到 'z' 的计数器。

c 遍历第一个字符串,将每个字符对应的计数器加1。

d 遍历第二个字符串,将每个字符对应的计数器减1。

e 最后检查所有计数器是否均为0。如果全是0,则是异位词;否则不是。

5 快乐数

(1)核心思想:使用哈希集合检测循环。如果某个中间结果重复出现,则说明进入循环,该数不是快乐数。

(2)算法描

a 初始化一个哈希集合 seen,用于记录已经出现过的数字。

b 当 n 不为1且不在 seen 集合中时,循环执行:

将 n 加入 seen。

将 n 替换为它每一位数字的平方和。

c 最终,如果 n 等于1,则是快乐数;否则不是。

三 中级算法

这类问题通常需要将哈希表与其他算法思想(如滑动窗口、前缀和、双向遍历等)结合,或解决更复杂的数据关系问题。

1 无重复字符的最长子串

(1) 核心思想:滑动窗口 + 哈希表。哈希表用于记录字符最近一次出现的索引,以便在遇到重复字符时快速收缩窗口左边界。

(2)算法描述:

a 初始化一个哈希表 charIndexMap,用于记录每个字符最新出现的索引。

b 使用左右指针 left 和 right 定义一个滑动窗口。

c 右指针不断向右移动,对于每个字符:

如果该字符已在哈希表中,并且其索引大于等于 left(即在当前窗口内),则将 left 指针移动到该字符旧索引的下一个位置。

更新该字符在哈希表中的索引为当前 right。

更新最大窗口长度。

2 字母异位词分组

(1)核心思想:使用规范化键(Canonical Key)。将异位词映射到同一个键上,这个键可以是排序后的字符串,也可以是字符频率统计的元组。

(2) 算法描述

a 初始化一个空字典 anagramMap,键是规范化表示,值是原字符串列表。

b 遍历字符串数组中的每一个字符串。

c 为当前字符串生成一个"键":

方法一(排序):将字符串排序,排序后的字符串作为键。

方法二(计数):创建一个长度为26的数组统计字符频率,将该数组或其元组形式作为键。

d 将原始字符串加入到该键对应的列表中。

e 返回字典中所有值的集合。

3 最长连续序列

(1)核心思想:利用哈希集合进行O(1)存在性检查,并只从序列的起点开始扩展,避免重复计算。

(2) 算法描述

a 将数组所有数字存入一个哈希集合 numSet。

b 遍历哈希集合中的每个数字 num。

c 检查 num - 1 是否存在于集合中。如果不存在,说明 num 是一个连续序列的起点。

d 从该起点开始,不断检查 num + 1, num + 2 ... 是否存在,并计算当前序列长度。

e 更新遇到的最大序列长度。

4 复制带随机指针的链表

(1)核心思想:使用哈希表建立 原节点 -> 新节点 的映射关系,分两遍遍历完成复制。

(2)算法描述

a 第一遍遍历:复制所有节点,并建立原节点到新节点的映射,存入哈希表 nodeMap。此时先不处理 random 指针。

b 第二遍遍历:对于每个原节点,其新节点的 next 指向 nodeMap[原节点.next],其新节点的 random 指向 nodeMap[原节点.random]。

5 四数相加

(1)核心思想:分组 + 哈希表。将四数组问题转化为两数组问题。

(2)算法描述

a 初始化一个空字典 sumCount,用于记录前两个数组元素和及其出现次数。

b 遍历数组A和B,计算所有可能的 a + b,并在 sumCount 中记录每个和出现的次数。

c 初始化计数器 count = 0。

d 遍历数组C和D,计算所有可能的 c + d,并检查 -(c + d) 是否在 sumCount 中。如果存在,则 count 加上 sumCount[-(c + d)]。

e 返回 count。

四 高级算法

这类问题通常涉及复杂的数据结构设计,要求对哈希表的底层原理有深刻理解,并能将其与其他高级数据结构(如双向链表、堆、单调栈等)巧妙结合。

1 实现LRU缓存

(1)核心思想:哈希表 + 双向链表。哈希表保证O(1)的访问,双向链表维护访问顺序。

(2)算法描述

a 数据结构:

一个哈希表 cache:存储 {key: ListNode}。

一个双向链表:头节点后是最近使用的,尾节点前是最久未使用的。

b get(key) 操作:

b1 如果 key 不存在,返回-1。

b2 如果存在,通过哈希表定位到节点,将该节点移动到链表头部(标记为最近使用),返回节点值。

c put(key, value) 操作:

c1 如果 key 已存在,更新值,并将节点移动到头部。

c2 如果不存在:

创建新节点,加入哈希表,并添加到链表头部。

如果容量超限,则删除链表尾部的节点(最久未使用),并在哈希表中删除对应的键。

2 实现LFU缓存

(1)核心思想:多层哈希表 + 双向链表。需要维护一个"频率"维度,将相同频率的节点放在同一个双向链表中。

(2)算法描述:

a 核心数据结构:

keyToNode: {key: Node},用于O(1)访问节点。

freqToDict: {frequency: DoublyLinkedList},每个频率对应一个双向链表,头尾分别是最近和最久。

minFreq:记录当前最小频率,用于淘汰。

b get(key) 操作:

b1 如果不存在,返回-1。

b2 如果存在,增加节点的频率,将其从原频率链表移除,加入到新频率链表的头部。如果原链表为空且是 minFreq,则更新 minFreq。

c put(key, value) 操作:

c1 如果 key 存在,更新值,并执行一次 get 操作(以更新频率)。

c2 如果不存在:

如果容量已满,从 minFreq 对应的链表尾部淘汰一个节点。

创建新节点(频率为1),放入 freqToDict[1] 的头部,并更新 minFreq = 1。

3 全O(1)数据结构

(1)核心思想:与LFU类似,但操作更复杂。需要支持所有计数字符串的获取,以及递增、递减操作。

(2) 算法描述:

a 数据结构:

一个哈希表 keyCount:存储 {key: count}。

一个哈希表 countKeys:存储 {count: Set(keys)},即每个计数值对应的所有键的集合。

维护 minCount 和 maxCount。

b inc(key):

b1 增加 key 的计数 count。

b2 将其从 countKeys[oldCount] 中移除,加入到 countKeys[newCount] 中。

b3 更新 minCount 和 maxCount。

c dec(key):与 inc 对称,减少计数并移动集合,注意更新 minCount。

d getMaxKey():返回 countKeys[maxCount] 中的任意一个键。

e getMinKey():返回 countKeys[minCount] 中的任意一个键。

4 数据结构设计:O(1)时间插入、删除和获取随机元素

(1)核心思想:动态数组 + 哈希表。数组提供O(1)的随机访问,哈希表提供O(1)的查找和删除定位。

(2)算法描述:

a 数据结构:

一个动态数组 list 存储元素。

一个哈希表 valToIndex 存储 {元素值: 在数组中的索引}。

b insert(val):

b1 如果 val 已存在,返回 false。

b2 将 val 追加到 list 末尾,并在 valToIndex 中记录其索引。

c remove(val):

c1 如果 val 不存在,返回 false。

c2 从 valToIndex 中获取 val 的索引 idx。

c3 将数组末尾元素 lastElement 与 idx 位置的 val 交换。

c4 更新 valToIndex[lastElement] 为 idx。

c5 删除数组最后一个元素,并从 valToIndex 中删除 val。

d getRandom():

d1 在 [0, list.size()-1] 范围内生成一个随机索引,返回 list 中该索引对应的元素。

五 总结

哈希表算法分三级:基础算法直接应用,如两数之和、频率统计,体现空间换时间。中级算法需结合其他思想,如滑动窗口解无重复子串、分组哈希处理异位词。高级算法聚焦复杂结构设计,如哈希表配双向链表实现LRU/LFU缓存,考验系统设计能力。

相关推荐
小白程序员成长日记2 小时前
2025.11.16 力扣每日一题
算法
爬山算法2 小时前
Redis(127)Redis的内部数据结构是什么?
数据结构·数据库·redis
Kuo-Teng3 小时前
LeetCode 118: Pascal‘s Triangle
java·算法·leetcode·职场和发展·动态规划
Greedy Alg3 小时前
LeetCode 32. 最长有效括号(困难)
算法
ShineWinsu3 小时前
对于数据结构:链式二叉树的超详细保姆级解析—中
数据结构·c++·算法·面试·二叉树·校招·递归
合方圆~小文4 小时前
高性能20倍变焦球机转动功能监控设备
数据结构·数据库·数码相机·模块测试
野蛮人6号4 小时前
力扣热题100道之207课程表
算法·leetcode·职场和发展
这周也會开心4 小时前
Map的遍历方式
数据结构·算法
liu****4 小时前
20.传输层协议TCP
服务器·网络·数据结构·c++·网络协议·tcp/ip·udp