一、Rabin-Karp算法
将字符串映射为一个Hash值,通过比较Hash值判断字符串是否相等。
选用的Hash函数:
把字符串看作一个b进制数(一个多项式),计算它(在十进制下)对p取模的值
举例:
取b=131,p=2^64
字符串foobar的Hash值为(a=1, b=2, f=6, o=15, r=18)
(6*1315+15*1314+15*1313+2*1312+1*131+18) mod 264
注:选取的b和p的值决定了Hash函数的质量,
根据经验,b=131,13331等,p为大质数,冲突概率小
Hash值相等时可以再对比一下两个字符串,避免Hash碰撞问题
计算一个子串的Hash值
s=“foobar”
计算6个前缀子串的Hash值,O(n):
H[i] = Hash (s [0…i-1]) = (H[i-1] * b + s[i-1]) mod p
计算子串oba的Hash值:
相当于b进制下两个数做减法(H[5] - H[2] * b3)
Hash(s[l…r]) = (H[r+1]-H[l]*br-l+1) mod p
class Solution {
private:
int p = 1e9+7;
int b = 131;
vector<long> p131;
private:
//计算子串hash值
long calcHash(vector<long>& sHash, int l, int r){
return return ((sHash[r]-sHash[l-1]*p131[r-l+1])%p+p)%p;
//注:有负数取模还在负数里面,需要加p再模p
}
public:
int strStr(string haystack, string needle) {
if(needle.size()==0) return 0;
int n = haystack.size();
int m = needle.size();
string s=" "+haystack;
string t=" "+needle;
//求s的所有前缀hash值
vector<long> sHash(n+1, 0);
for(int i=1; i<=n; ++i){
sHash[i] = (sHash[i-1]*b+s[i]-'a'+1)%p;
}
//求t的hash值
long tHash = 0;
for(int i=1; i<=m; ++i){
tHash = (tHash*b+t[i]-'a'+1)%p;
}
//求进制
p131 = vector<long>(n+1);
p131[0] = 1;
for(int i=1; i<=n; ++i){
p131[i]=p131[i-1]*131%p;
}
//匹配过程
for(int i=m; i<=n; ++i){
if(calcHash(sHash, i-m+1, i)==tHash && s.substr(i-m+1, m)==needle){
return i-m;
}
}
return -1;
}
};
二、字符串匹配——KMP
class Solution {
private:
void getNext(vector<int>& next, string needle){
int j=0;//指向前缀末尾,同时代表最长相等前后前后缀的个数
next[0]=0;
//i指向后缀末尾
for(int i=1; i<needle.size(); ++i){
while(j>0 && needle[i]!=needle[j]){
j = next[j-1];//找j前一位的next
}
if(needle[i]==needle[j]) j++;
next[i]=j;
}
}
public:
int strStr(string haystack, string needle) {
vector<int> next(needle.size());
getNext(next, needle);
int j=0;//指向needle中正在匹配的位置
for(int i=0; i<haystack.size(); ++i){
while(j>0 && haystack[i]!=needle[j]){
j = next[j-1];
}
if(haystack[i]==needle[j]) j++;
if(j==needle.size()) return i-j+1;
}
return -1;
}
};