字符串哈希 理论篇

102 阅读2分钟

视频教程: F02 字符串哈希_哔哩哔哩_bilibili

字符串哈希是为了解决字符串匹配问题,方法就是把每个字符映射成不同的数字.

为什么不能直接比较两个字符串相等呢,我们通过的比较两个字符串是否相等if(s1==s2)实际上是在每一位进行比较,复杂度最坏为O(n)的

例如下题:841. 字符串哈希 - AcWing题库

使用substr()提取每个子串,每个子串进行比较,时间复杂度为O(n),假设m最大为n,一共就有O(n2)O(n^{2})的复杂度.就会TLE:

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,m;cin>>n>>m;
    
    string s;cin>>s;
    s="#"+s;   //为了让下标从1开始
    while(m--)
    {
        int a,b,c,d;cin>>a>>b>>c>>d;
        string s1=s.substr(a,b-a+1),s2=s.substr(c,d-c+1);
        
        //cout<<s1<<" "<<s2<<endl;
        if(s1==s2)
        {
            cout<<"Yes"<<endl;
        }    
        else cout<<"No"<<endl;
    }

    
    return 0;
}

image.png

而字符串哈希可以预处理好每个子串的哈希值,这样在比较每个每个子串是否相等时,只需要比较两个字符串的哈希值就可以判断出两个子串是否相等,只需要O(1)复杂度.假设m最大为n,总共只需要O(n)复杂度

具体操作:

首先用前缀思想,开一个h[]数组,h[i]存储每一个字符串前缀经过处理过后的哈希值,这样就可以在查询时O(1)时间内通过比较两个子串的哈希值是否相等来判断两个子串是否相同了 image.png

哈希化的操作:

我们需要把s[i]的每一位都乘PniP^{n-i},P通常取131或13331

又因为字符串可能特别长,比如几十万,所以最后的哈希值也会非常大,因此我们需要将其对一个数取模,这个数我们通常用mod表示,mod通常取值为2642^{64}

因为mod是 $2^{64},所以我们通常用一个unsigned long long 类型来存储h[]数组和p[]数组,这样我们就不用手动取模了,会自动溢出. image.png

#include<iostream>
using namespace std;
typedef unsigned long long ULL;
const int P=131;
const int N=1e5+10;
char s[N];
ULL h[N],p[N];
int n,m;
ULL get(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    scanf("%d%d%s",&n,&m,s+1);

    p[0]=1;
    for(int i=1;i<=n;i++)
    {
        p[i]=p[i-1]*P;
        h[i]=h[i-1]*P+s[i];
    }
    
    while(m--)
    {
        int l1,r1,l2,r2;scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(get(l1,r1)==get(l2,r2))puts("Yes");
        else puts("No");
    }
    return 0;
}

image.png