字符串哈希
本文最后更新于:2024年2月12日 晚上
字符串哈希
使用固定的流程,将任意长度的字符串映射成为一个数字—-哈希值,并且相同的字符串拥有相同的哈希值。
对字符串进行哈希映射后,可以在$O(1)$的时间内判断该字符串的任意子串是否相等。
哈希过程:
1.选取一个基数base
表示是几进制数,一般选base = 131, 或者 base = 13331
,这样产生哈希冲突的概率极低。
2.通常使用long
存储哈希值,当计算出的哈希值超过long
范围时,产生的溢出相当于直接做了取模运算,从而可以在计算时不使用取模运算。(取模值M
=$2^{64}-1$)
3.运算法则:
已知字符串
S
的哈希值H(S)
,那么在其后面添加一个字符c
,得到的字符串的哈希值为:$H(S+c)=(H(S)*base+value(c))\%M$,该计算相当于在base
进制下进行左移。可以看成是:从后往前计算base进制数的过程
已知字符串
S+T
的哈希值为H(S+T)
,S
的哈希值为H(S)
,那么T
的哈希值为:$H(T)=(H(S+T)-H(S)*base^{len(T)})\%M$,该计算相当于在base
进制下,将H(S)
左移补零,使得与`H(S+T)左端对齐再相减。
4.计算技巧:在计算前缀哈希值时,可以顺便计算$base^{n}$这样就省去了独立计算$base^{len(T)}$的时间。
题目:
class Solution {
public String longestPrefix(String s) {
int n = s.length();
int base = 131; // 表示131进制,(p = 13331)
long[] hash = new long[n + 1]; // 0~i前缀字符串的哈希值
long[] p = new long[n + 1]; // 保存base的几次方
p[0] = 1; // base零次方等于1
// 计算前缀哈希值
for (int i = 1; i <= n; i++) {
// 计算哈希值:隐含取模 Long.MAX_VALUE
hash[i] = hash[i - 1] * base + s.charAt(i - 1) - 'a';
// 保存base的i次方值,方便计算任意子串的哈希值
p[i] = p[i - 1] * base;
}
// 计算后缀哈希值
for (int i = n - 1; i >= 1; i--) {
long pre = hash[i]; // H(S)
// H(T) = (H(S+T) - H(S) * base ^ len(T))
long back = hash[n] - hash[n - i] * p[i]; // 后缀哈希值
// 前后缀哈希值相等,可以认为两字符串相等
if (pre == back) {
return s.substring(0, i);
}
}
return "";
}
}
利用质数计算字符串哈希值
将每个字母分别映射到一个质数,计算哈希值时使用乘法,由于乘法满足交换率,字母映射到质数,所以不互为「变位词」的字符串计算得到的哈希值是不一样的。
🐷:只适用于字符串长度较小的case
int[] hash = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,
47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101};
long hashCode = 1l;
for (int i = 0; i < s.length(); i++) {
hashCode *= hash[s.charAt(i) - 'a'];
}
本文作者: MerickBao
本文链接: https://merickbao.top/post/%E7%AE%97%E6%B3%95/%E8%BF%9B%E9%98%B6%E7%AE%97%E6%B3%95/%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%93%88%E5%B8%8C.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!