知识总结:字符串哈希| 豆包MarsCode AI刷题

192 阅读3分钟

简介

通过Hash函数,将一个字符串映射成一个整数。这样我们就能很方便的使用Hash函数来判断两个字符串是否相等

思想

Hash函数的核心思想就是将输入值映射到一个值域较小,可以比较的范围,通常是整数。

这里的值域较小也不能太小,不然哈希冲突率会很高。 这里的值域较小在不同情况下意义不同。 对于哈希表,值域要小到线性的时空复杂度 而对于字符串哈希,只需要这个值域内的数字能快速比较就行。

哈希值(Hash函数)的特性

  • 确定性:同样的字符串总是生成相同的哈希值。
  • 高效性:计算速度快。
  • 均匀性:不同的字符串尽量映射到不同的哈希值,减少冲突。
  • 在 Hash 函数值不一样的时候,两个字符串一定不一样
  • 在 Hash 函数值一样的时候,两个字符串不一定一样

计算

最常见的方式就是多项式哈希,假设字符串下标从1开始

Hash(S)=(i=1len(s)S[i]bleni)modMHash(S)= (\sum_{i=1}^{len(s)}S[i]*b^{len-i})modM
  • S[i] 通常是字符串中第 i 个字符的 ASCII 或 Unicode 值。
  • P 是一个选择的常数,通常为31或37,131,13331等素数。
  • M 是一个大素数,用于减少哈希冲突。

这种方法可以类比为一个P进制数来帮助理解。

Hash的分析与改进

减少哈希碰撞

为了减少哈希碰撞,最简单的方式就是使用合理且尽量大的素数M作为模数

在进行字符串哈希时,我们经常使用双哈希法,可以使用两个哈希函数,然后用加权的方式相加,或者直接比较两个哈希值的方式,这样的话哈希函数的值域就能扩大很多了且冲突率下降很多。

还可以用多哈希加权,哈希值字符串拼接,动态哈希选择等方式

多次询问子串哈希

单次计算一个字符串的哈希值是线性的。如果多次询问一个字符串的子串的哈希值,每次重新计算效率会十分低下。

一般采取的方法是对整个字符串先预处理出每个前缀的哈希值,将哈希值看成一个p进制的数对 M 取模的结果,这样的话每次就能快速求出子串的哈希了。

Hash(S[l...r])=(i=lrS[i]Pri)modMHash(S[l...r])=(\sum_{i=l}^{r}S[i]*P^{r-i} ) \,mod\, M
Hash(S[1...r])=(i=1rs[i]Pri)modM{Hash}(S[1 ... r])=(\sum_{i=1}^{r} s[i] * P^{r-i}) \bmod M
Hash(s[1..l1])=(i=1l1S[i]Pli1)modMHash(s[1 .. l-1])=(\sum_{i=1}^{l-1} S[i] * P^{l-i-1}) \bmod M
Hash(s[l,r])(Hash(s[1...r])Hash(s[1...l1])Prl+1)(modm)Hash(s[l, r]) \equiv (Hash(s[1 ... r])-{Hash}(s[1 ... l-1]) \cdot P^{r-l+1} \quad )(\bmod m)

这样通过预处理,就能把截取子串哈希变成O()1了

应用

字符串匹配

求出模式串的哈希值,然后扫一遍文本串每个长度为模式串长度的字符串,判断一下哈希值是否相等即可

最长回文子串

二分答案最长回文子串长度,check时枚举回文子串的对称轴,哈希值判断两侧是否相等(这步需要分别预处理正着和倒着的哈希值)。时间复杂度为O(nlogn)O(n log n)

也可以使用manacher算法在线性时间结束

最长公共子字符串

给定m个长度不大于n的字符串,求出所有字符串最长公共子字符串。 很显然这题可以二分答案最长公共子字符串长度,假设目前枚举的答案是k,那么check的逻辑就是将所有字符串长度为k的子串分别进行哈希,将哈希值放入n个哈希表中,然后判断是否存在交集即可。

时间复杂度为O(m+ nlogn)