Leetcode数据结构&算法:哈希表

一、简介

哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。

有两种不同类型的哈希表:哈希集合和哈希映射。

  • 哈希集合集合数据结构的实现之一,用于存储非重复值
  • 哈希映射映射 数据结构的实现之一,用于存储(key, value)键值对。

标准模板库的帮助下,哈希表是易于使用的。大多数常见语言(如Java,C ++ 和 Python)都支持哈希集合和哈希映射。

通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能

PS:下文三个实际应用先纯文字记录下,到时候做了题再把代码加进去。

 二、哈希表

哈希表的关键思想是使用哈希函数将键映射到存储桶

  1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
  2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。

 举个栗子:

 

我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略:

  1. 插入:我们通过哈希函数解析键,将它们映射到相应的桶中。
    例如,1987 分配给桶 2,而 24 分配给桶 4。

  1. 搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。

    如果我们搜索 1987,我们将使用相同的哈希函数将1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。
    例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。

在设计哈希表时,要注意两个因素:哈希函数和解决冲突。

(1)哈希函数

哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。上例中,我们使用 y = x % 5 作为散列函数,其中 x 是键值,y 是分配的桶的索引。

散列函数将取决于键值的范围桶的数量

一些哈希函数的示例:

哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡

(2)解决冲突

理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(y  =  x % 5)中,1987 和 2 都分配给了桶 2,这是一个冲突

冲突解决算法应该解决以下几个问题:

  1. 如何组织在同一个桶中的值?
  2. 如果为同一个桶分配了太多的值,该怎么办?
  3. 如何在特定的桶中搜索目标值?

根据我们的哈希函数,这些问题与桶的容量和可能映射到同一个桶键的数目有关。

让我们假设存储最大键数的桶有 N 个键。

通常,如果 N 是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 N 是可变的或很大,我们可能需要使用高度平衡的二叉树来代替.。

 三、实际应用-哈希集合

 哈希集是集合的实现之一,它是一种存储不重复值的数据结构。

 我们知道,插入新值并检查值是否在哈希集中是简单有效的。因此,通常,使用哈希集来检查该值是否已经出现过。

举个栗子,

问题:给定一个整数数组,查找数组是否包含任何重复项。

这是一个典型的问题,可以通过哈希集来解决。 你可以简单地迭代每个值并将值插入集合中。 如果值已经在哈希集中,则存在重复。

/*
 * Template for using hash set to find duplicates.
 */
bool findDuplicates(vector<Type>& keys) {
    // Replace Type with actual type of your key
    unordered_set<Type> hashset;
    for (Type key : keys) {
        if (hashset.count(key) > 0) {
            return true;
        }
        hashset.insert(key);
    }
    return false;
}

四、实际应用-哈希映射

(1) 场景一:

使用哈希映射的第一个场景是,我们需要更多的信息,而不仅仅是键。然后通过哈希映射建立密钥与信息之间的映射关系

 举个栗子,

问题:给定一个整数数组,返回两个数字的索引,使它们相加得到特定目标。

要求返回更多信息,这意味着我们不仅关心值,还关心索引。我们不仅需要存储数字作为键,还需要存储索引作为值。因此,我们应该使用哈希映射而不是哈希集合。

在某些情况下,我们需要更多信息,不仅要返回更多信息,还要帮助我们做出决策

在前面的示例中,当我们遇到重复的键时,我们将立即返回相应的信息。但有时,我们可能想先检查键的值是否可以接受。

 (2)场景二:

另一个常见的场景是按键聚合所有信息。我们也可以使用哈希映射来实现这一目标。

举个栗子,

问题:给定一个字符串,找到它中的第一个非重复字符并返回它的索引。如果它不存在,则返回 -1。

解决此问题的一种简单方法是首先计算每个字符的出现次数。然后通过结果找出第一个与众不同的角色。

因此,我们可以维护一个哈希映射,其键是字符,而值是相应字符的计数器。每次迭代一个字符时,我们只需将相应的值加 1。

 解决此类问题的关键是在遇到现有键时确定策略

在上面的示例中,我们的策略是计算事件的数量。有时,我们可能会将所有值加起来。有时,我们可能会用最新的值替换原始值。策略取决于问题,实践将帮助您做出正确的决定。

五、实际应用- 设计键

 有时你必须考虑在使用哈希表时设计合适的键

 问题:给定一组字符串,将字母异位词组合在一起。

 此处不能直接使用原始字符串作为键。我们必须设计一个合适的键来呈现字母异位词的类型。

 实际上,设计关键是在原始信息和哈希映射使用的实际键之间建立映射关系。设计键时,需要保证:

  1. 属于同一组的所有值都将映射到同一组中。
  2. 需要分成不同组的值不会映射到同一组。

此过程类似于设计哈希函数,但这是一个本质区别。哈希函数满足第一个规则但可能不满足第二个规则。但是你的映射函数应该满足它们。

在上面的问题中,我们的映射策略可以是:对字符串进行排序并使用排序后的字符串作为键。也就是说,“eat” 和 “ate” 都将映射到 “aet”。

这里有一些如何设计键的建议:

  1. 当字符串 / 数组中每个元素的顺序不重要时,可以使用排序后的字符串 / 数组作为键。

  2. 如果只关心每个值的偏移量,通常是第一个值的偏移量,则可以使用偏移量作为键。

  3. 在树中,你有时可能会希望直接使用 TreeNode 作为键。 但在大多数情况下,采用子树的序列化表述可能是一个更好的主意。

  4. 在矩阵中,你可能希望使用行索引列索引作为键。
  5. 在数独中,可以将行索引和列索引组合来标识此元素属于哪个

  6. 有时,在矩阵中,您可能希望将值聚合在同一对角线中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值