每日一水(哈希表) | 豆包MarsCode AI刷题

118 阅读5分钟

一、定义

哈希表(Hash Table),也叫散列表,是一种数据结构。它通过一个哈希函数(Hash Function)将数据元素的关键字(Key)映射为一个数组下标的位置,这个位置用来存储该元素。简单来说,哈希表就像是一个带有索引系统的存储仓库,这个索引系统(哈希函数)能够快速地告诉我们某个数据应该放在哪里。

例如,假设有一个图书馆(哈希表),每一本书(数据元素)都有一个编号(关键字),通过某种规则(哈希函数),我们可以根据这个编号快速找到这本书应该放在哪一个书架(数组位置)。

二、哈希函数

  1. 作用

    • 哈希函数是哈希表的核心部分。它的主要功能是将输入的关键字转换为一个在哈希表范围内的索引值。例如,对于一个长度为 n 的数组(哈希表的存储区域),哈希函数要把各种可能的关键字转换为 0 到 n - 1 之间的整数。
  2. 特性要求

    • 确定性:对于相同的关键字,哈希函数必须总是返回相同的索引值。就像如果一本书的编号是固定的,每次通过哈希函数计算它应该放的书架位置都应该是一样的。
    • 高效性:计算哈希函数的值应该是非常快速的。因为在实际的数据存储和查找过程中,哈希函数会被频繁调用,如果计算速度很慢,会影响整个哈希表的性能。
    • 均匀分布:理想情况下,哈希函数应该将不同的关键字均匀地分布在哈希表的各个位置。如果哈希函数设计得不好,可能会导致很多关键字都映射到了同一个位置,这就会产生冲突(后面会详细介绍)。
    • 例如,一个简单的哈希函数可以是取关键字对哈希表大小(数组长度)取余数。如果哈希表大小为 10,关键字为 7,那么哈希函数计算结果为 7 % 10 = 7,这个数据就会被存储在数组索引为 7 的位置。

三、冲突解决

  1. 什么是冲突

    • 当两个或多个不同的关键字通过哈希函数计算得到相同的索引值时,就产生了冲突。例如,在刚才图书馆的例子中,如果两本不同编号的书通过哈希函数计算后都应该放在同一个书架上,这就是冲突。
  2. 解决方法

    • 开放定址法

      • 当发生冲突时,使用某种探测序列在哈希表中寻找下一个可用的位置。常见的探测方法有线性探测(顺序地查找下一个位置)、二次探测(按照二次函数的规律查找下一个位置)等。
      • 例如,使用线性探测,假设哈希函数计算出的初始位置为 i,发生冲突后,就依次检查 i + 1、i + 2 等位置,直到找到一个空的位置来存储数据。
    • 链地址法

      • 在每个哈希表的数组元素位置上,维护一个链表(或者其他数据结构,如树)。当有冲突时,将新的数据元素添加到这个链表中。这样,所有哈希到同一位置的元素都可以存储在这个链表中。

      • 例如,在一个哈希表的某个位置,已经有一个元素 A,当有一个新元素 B 和 A 冲突时,就把 B 添加到 A 所在位置对应的链表中,形成一个元素链。

四、应用场景

  1. 数据库索引

    • 哈希表可以用来快速查找数据库中的记录。在数据库系统中,通过对表的主键等关键字构建哈希表,可以大大提高数据查询的速度。例如,在一个用户信息表中,用户 ID 是唯一的,通过将用户 ID 作为关键字构建哈希表,当需要查找某个用户的信息时,就可以快速定位到存储该用户信息的位置。
  2. 缓存系统

    • 许多缓存系统使用哈希表来存储缓存数据。比如,在一个网页缓存中,根据网页的 URL(关键字)来存储和查找缓存的网页内容。哈希表能够快速判断一个请求的网页是否已经在缓存中,从而提高网页加载速度。
  3. 编程语言中的字典类型

    • 在 Python 中的字典(dict)、Java 中的 HashMap 等数据类型本质上都是哈希表的实现。它们允许用户以键 - 值对的形式存储和访问数据,通过键来快速查找对应的值。

    • C++ 标准库提供了std::unordered_map作为哈希表的实现。

    • 它的特点是元素的存储位置是通过哈希函数来确定的,这样在理想情况下,插入、删除和查找操作的平均时间复杂度可以达到O(1)不过在最坏情况下,由于可能出现哈希冲突等情况,时间复杂度可能会退化为O(n)

题目

现在来个水题帮助学习哈希表

问题描述

给定一个字符串 ss,该字符串中只包含英文大小写字母。你需要计算从字符串中最多能组成多少个字符串 "ku"。每次可以随机从字符串中选一个字符,并且选中的字符不能再使用。字符串中的字符大小写可以忽略,即大写和小写字母视为相同。

例如,输入 "AUBTMKAxfuu",从中最多能组成 1 个 "ku"

解题思路

  1. 字符计数:我们需要统计字符串中每个字符的出现次数,特别是字符 'k' 和 'u'
  2. 忽略大小写:在统计时,将所有字符转换为小写(或大写),以便统一处理。
  3. 计算组合数:字符 'k' 和 'u' 的组合数取决于两者中较小的数量,因为每个 "ku" 需要一个 'k' 和一个 'u'

代码如下

#include <iostream>
#include <unordered_map>
#include <string>

using namespace std;
int solution(const string& s) {
    unordered_map<char, int> num;
    for(char str: s){
        // 将字符转换为小写
        char lower_str = tolower(str);
        num[lower_str]++;
    }
    return min(num['k'], num['u']);
}