数据结构 哈希表 字符串哈希

字符串哈希表

将字符串转换为一个P进制的纯数字

每个字符,代表P进制上的一位。

abc和ab中的a,代表的值就不一样了

一个a是a的asc码*p的一次方,一个是a是a的asc码的*p的二次方

如图,abcde字符串对应的其哈希值

如上图所示

五个数的和,就是字符串的哈希值,这哈希值是唯一的

只有字符相同,长度相同,字符所在位置完全相同,字符串的哈希值才完全相同

为了避免哈希值重复,我们一般选择p=131或者p=13331

p的值

一般选择p=131 或 p=13331

  • 质数的特性:131131 和 1333113331 是质数。质数作为基数能更均匀地分散字符串的哈希值,避免因字符分布规律导致的碰撞。
  • 经验验证:这两个值在实践中被广泛测试,证明它们在绝大多数场景下能有效降低冲突概率。
  • 自然溢出优化:当使用 unsigned long long 存储哈希值时,P×字符asc码 的乘法会自然溢出(等价于模 2的六十四次方),无需显式取模。

哈希值前缀和数组推导公式

h[N]设置为存储哈希值的数组,例如h[i],就是字符串第1个到第i个的哈希值

str[i]代表字符串中第i个字符的asc码

则h[i]=

h[i] = h[i-1]*P+str[i];

i-1是字符串的前一位

例如ab字符串添加c

ab*p,p进制哈希值,代表ab都进一位,得结果为ab0,添加c,得abc字符串哈希值

利用字符串哈希表查询

字符串哈希的题,往往会让我们查找L1-R1和L2-R2是否为相同字符串

拆开来看,就是查找字符串,指定区间内的哈希值

其实我们直接用前缀和的思想思考即可

例如查找l-r区间内字符串的哈希值

h[r]是前r个字符串的哈希值

h[l-1]是L前的字符串的哈希值

举例

字符串abcde,求3-5区间的哈希值,r为5,l为3

h[5]为h[r],也就是h[r]的哈希值是abcde

然后h[l-1],也就是h[2]的哈希值是ab

我们想求的l-r区间,实际上就是想求cde的哈希值

如果得到cde的哈希值,需要减去ab

但是h[2]的哈希值的ab,是两位数ab

abcde里的ab,实际上是ab000,所以想要得到cde的哈希值,需要ab000-abcde

我们知道,字符串的哈希值是p进制,那让ab变成ab000,只需要ab*p*p*p

也就是ab*p的(r-l+1)次方

设X为L-R字符串区间哈希值,得公式

ULL  x=h[r]-h[l-1]*p[r-l+1];

经典例题和完整代码

AcWing - 算法基础课

#include<iostream>
using namespace std;
#define ULL unsigned long long
// typedef unsigned long long ULL;
const int N = 100003;
const int P = 131;
int p[N],h[N];//p[i]存储的是p的i次方,例如p[2]存储p的2次方
//返回区间字符串的哈希值
ULL que(int l,int r){
    return h[r]-h[l-1]*p[r-l+1];
}
int main(){
    int n,m;
    cin>>n>>m;
    char str[N];
    cin>>str+1;
    p[0]=1;
    h[0]=0;
    for(int i=1;i<=n;i++){
    //同时初始化p[N],存储p的次方
    p[i] = p[i-1]*P;
    //前缀和求整个字符串哈希值   
    h[i] = h[i-1]*P+str[i];
    }
    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        cout<<(que(l1,r1)==que(l2,r2)?"Yes":"No")<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值