哈希表——总结篇

0 阅读7分钟

这篇题解是关于前几篇哈希题解的总结篇,题目链接:有效的字母异位词 242两个数组的交集 349两数之和 1三数之和 15四数之和 18快乐数 202赎金信 383。主要包含哈希表理论哈希碰撞数组,set 和 map 在哈希表中的应用以及N数之和总结

哈希表理论知识

哈希表是一种根据关键码的值而直接进行访问的数据结构。通俗来讲就是可以通过关键字快速找到数据,就像超市称重台的电子秤,输入一串数字总能显示出对应的商品名,当然超市中的商品值是人为事先编排好的,而哈希表是通过哈希函数实现的。

什么是哈希函数? 如下图所示,将水果通过英文首字母映射到数组中就可以视为简单的哈希映射,而其中这套通过首字母映射的规则就是哈希函数。 哈希表——总结篇_20260730_111552.png 一般而言,哈希函数就是通过一些特定的编码方式,将其他数据格式转换为数值的。简单的哈希函数构造法包括直接定址法,除留余数法等等,复杂的哈希函数会有更为严谨的设计和构造过程,例如 Google 开发的 CityHash 就是一种优秀的哈希算法。


哈希碰撞

如下图所示,当列表中增添一个 蓝莓 blueberry 时,会发现映射到数组中也是索引为 1 的元素 b,这种情况就称为哈希碰撞,也叫哈希冲突哈希表——总结篇_20260730_142931.png 实际上,几乎不可能编写出永远能将不同关键字映射到不同位置的哈希函数,因此出现哈希碰撞是很常见的,一般我们通过计算装填因子来判断是否需要对哈希表调整,当装填因子大于0.7时便有必要调整哈希表容量了。装填因子记做 α,计算公式为:

α=哈希表包含的元素数/位置总数α = 哈希表包含的元素数/位置总数

例如下图中哈希表的装填因子就是 α=2/5=0.4α = 2/5 = 0.4哈希表——总结篇_20260730_144326.png 既然哈希碰撞是很常见的,那么出现哈希碰撞应该怎么解决呢? 常用的两种方法是线性探测法拉链法,下面是这两种方法的简单操作介绍。

线性探测法

所谓线性探测法就是依靠哈希表中的空位来处理碰撞问题,简单来说就是从碰撞位置不断向下寻找空位,找到第一个空位后便将碰撞值放入,如下图所示: 哈希表——总结篇_20260730_145402.png 在这个例子中发生碰撞的位置是 b处 ,向下寻找空位发现的第一个空位是 d处 ,因此便将碰撞值 蓝莓 放到了 d处,这个过程就是简单的线性探测法操作过程。

需要注意的是:

  • 哈希表的位置一定大于存放元素的数量,否则将没有空位用来存放碰撞值
  • 连续的占用空位形成区块,新数据冲突时更容易落到这块区域,冲突扎堆、越长越大
  • 不能直接删除某个元素,应该设置删除标记,否则整个线性查询将中断
拉链法

拉链法是指直接在发生碰撞位置设置链表,依次存储碰撞值的方法,如下图所示: 哈希表——总结篇_20260730_150915.png 也要注意

  • 如果大量元素集中在同一个链表,链表过长,查询效率退化至 O(n)O (n)
  • 需要额外开销存储链表指针,存在少量内存开销

数组在哈希表中的应用

哈希数组的应用主要体现在两道题目中,分别是 有效的字母异位词 242赎金信 383

实际上,这两道题目的大体思路很相似:

  • 定义长度为 26 的 record 数组用于记录各字母出现次数
  • 先对第一个数据进行遍历,各个字母每出现一次,对应索引处元素便 +1
  • 再对第二个数据遍历,各字母每出现一次,record中对应元素便 -1
  • 最后通过判断record数组中各元素是否为零

在哈希表中要使用数组需要满足的条件是元素有限、范围连续且数量较少,例如上述两题元素均为小写字母。这是因为数组的操作原理是先划分空间再进行存储,因此要求有限个,而连续且少量是因为数据范围过广,量过大时再使用数组会占用大量内存

哈希数组的本质是直接寻址哈希表,不存在哈希碰撞,效率高于其他容器的哈希表,但缺点是需要提前开辟整块空间,键范围跨度很大时不能使用,这时就需要选择 map。


set容器在哈希表中的应用

两个数组的交集 349 是使用 set容器 解决哈希问题的典型例题,在题解中对比了四类常见容器的特点并得出由于题给条件是 单值无序 因此选用 unordered_set 是最优解。当然在力扣后期修改题目后给定了数据范围,这道题也就可以使用哈希数组来实现。其简单思路如下图: 两个数组的交集  349_20260725_151930.png

在 C++ 中,提供了三种 set 容器,分别是 setmultisetunordered_set 。其中setmultiset的底层结构是红黑树,而unordered_set的底层结构是哈希表。

快乐数 202 中也使用了 unordered_set 哈希结构,用于判断 sum 是否重复出现。同时这道题目还可以使用 快慢指针法 解决,通过成环后两指针相遇位置对应的数值来判断是否为快乐数,具体原理详见本篇题目题解以及 环形链表Ⅱ 142


map容器在哈希表中的应用

map 在哈希表中的应用主要体现在 两数之和 1 ,相较于数组和 set ,map 的优点在于 map 是一种<key, value>的结构,在本题中可以用 key 保存数值,用 value 保存数值所在的下标。

与 set 容器类似,C++也提供了三种 map 容器:mapmultimapunordered_map,其中mapmultimap的底层结构是红黑树,unordered_map的底层结构是哈希表。

与 set 容器同理,mapmultimap 的 key 也是有序的,而本题中并不需要 key 有序,因此选择 unordered_map 效率更高。


N数之和

N数之和是一类典型算法题,主要包含 两数之和 1三数之和 15四数之和 18 。其中两数之和使用 map 哈希容器容易解题,而三数之和与四数之和则更适合使用双指针解题,根本原因是双指针可以有效去重,而哈希处理三数、四数去重极其麻烦。

三数之和与四数之和的算法逻辑大体相似:

  • 排序数组(双指针前提,同时方便去重)
  • 递归 / 循环固定前 k-2 个数
  • 最后剩下两个数,使用双指针查找
  • 每一层循环都进行去重,避免重复组合

两数之和不适合使用双指针,核心原因是双指针需要排序,排序后原始下标会丢失,无法返回索引,而 map 正好有 key 和 value 两个值,可以同时存储,因此两数之和更适合用哈希。


总结

哈希刷题快速选型:

  1. 字符 / 数字范围连续且很小 → 数组哈希,效率最高
  2. 仅需判断元素是否存在、无需存储附加信息 → unordered_set
  3. 需要键值配对存储(下标、计数)→ unordered_map
  4. 需要自动有序结果 → set/map
  5. 两数之和要下标用 unordered_map
  6. N数之和(除两数之和)优先排序 + 双指针。