CHAPTER_12 提高篇(6)——字符串专题
12.1 字符串hash进阶
在4.2节中我们层曾提到字符串hash,字符串hash是指将一个字符串S映射为一个整数。
在之前的介绍中,我们将一个n位字符串看作一个n位26进制数,即字母a~z代表0~25,然后使用进制转换的方式将其转换成10进制数完成映射。然而这种方式不适用于长度较大的字符串,因为转换出的整数也会很大,为了应对这种情况,我们只能舍弃一些"唯一性",即对一个转换得到的整数mod取模。这样成功使整数变小,但是可能会产生hash冲突,即两个不同的整数取模后得到相同的数。
幸运的是,在实践中发现,在int数据范围内,如果把进制数p设置为一个10^7级别的质数(例如10000019),同时把mod设置为一个10^9级别的质数(例如1000000007)。这样产生冲突的概率很小,并且能有效压缩数据。用H[i]表示字符串前 i 个的hash值,则求解如下面公式所示:
我们来看一个问题。
题目:
输入两个长度均不超过1000的字符串,求它们的最长公共子串的长度(子串必须连续)。例如''ILoveYou“和”YouDontLoveMe“的最长公共子串是"Love",长度为4。
输入样例:
ILoveYou
YouDontLoveMe
输出样例:
4
思路:
我们在11.4节中介绍了最长公共子序列的动态规划求法,实际上最长公共子串也可以用dp求解。与子序列问题不同的是,子串必须连续,因此dp数组的设置会有些不同。详情可参考博客:
【动态规划】最长公共子序列和最长公共子串(python)_机器学习初学者必看,关注我,一起了解机器学习-CSDN博客
我们这里讨论如何使用字符串hash来解决最长公共子串问题。我们分别对两个字符串的每个子串求出hash值(同时记录对应长度),然后找出两堆子串中对应的hash值相等的那些,便可以找到最大长度。时间复杂度为O(n^2+m^2)。
我们要考虑如何对子串求hash值,也就是求解子串str[i...j]的hash值H[i...j]。经过数学推导,我们直接给出H[i...j]的公式:
观察上面公式可以看出,为了随时能够求出H[i...j],我们必须要用一个数组H[]来保存该字符串的hash值,其中H[i]表示前 i 位的hash值。同时,因为我们需要一直计算p^(j-i+1),不妨使用一个powP数组来记录p的幂,这样可以节省重复的计算。
参考代码:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<utility>
using namespace std;
typedef long long LL;
const LL mod=1000000007;
const LL p=10000019;
const int maxn=1010;
LL powP[maxn]; //powP[i]存放p^i%mod
LL H1[maxn]={0},H2[maxn]={0}; //存放str1,str2的hash值
vector<pair<int,int> > pr1,pr2; //分别存放str1,str2的所有子串的hash值和对应长度
//初始化powP函数
void init(int len) {
powP[0]=1;
for(int i=1;i<=len;i++) {
powP[i]=(powP[i-1]*p)%mod;
}
}
//计算字符串str的hash值
void calH(LL H[],string &str) {
H[0]=str[0]; //H[0]单独处理
for(int i=1;i<str.size();i++) {
H[i]=(H[i-1]*p+str[i])%mod;
}
}
//计算单个H[i...j]
int calSingleSubH(LL H[],int i,int j) {
if(i==0)
return H[j];
else {
return ((H[j]-H[i-1]*powP[j-i+1])%mod+mod)%mod;
}
}
//计算所有子串的hash,并将<hash,长度>存入vector
int calSubH(LL H[],int len,vector<pair<int,int> > &pr) {
for(int i=0;i<len;i++) {
for(int j=i;j<len;j++) {
int hashValue=calSingleSubH(H,i,j);
pr.push_back(make_pair(hashValue,j-i+1));
}
}
}
//计算pr1和pr2中相同的最大hash值 ]
int getMax() {
int ans=0;
for(int i=0;i<pr1.size();i++) {
for(int j=0;j<pr2.size();j++) {
if(pr1[i].first==pr2[j].first) {
ans=max(ans,pr1[i].second);
}
}
}
return ans;
}
int main() {
string str1,str2;
cin>>str1>>str2;
init(max(str1.size(),str2.size())); //初始化powP数组
calH(H1,str1);
calH(H2,str2); //分别计算str1,str2的hash值
calSubH(H1,str1.size(),pr1);
calSubH(H2,str2.size(),pr2); //计算子串hash值
cout<<getMax()<<endl;
return 0;
}