《算法笔记》读书记录DAY_60

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值,则求解如下面公式所示:

H[i]=\left ( H[i-1]\times p+index\left ( str[i] \right ) \right )%mod

我们来看一个问题。

题目:

输入两个长度均不超过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]=\left ( \left ( H[j]-H[i-1]\times p^{j-i+1} \right )%mod+mod \, \, \right )%mod

\left (\, len> j\geq i> 0 \right )

观察上面公式可以看出,为了随时能够求出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;
} 

猜你喜欢

转载自blog.csdn.net/jgsecurity/article/details/121372481